@agimon-ai/mcp-proxy 0.4.1 → 0.4.3
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 +220 -181
- package/dist/cli.mjs +222 -184
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-DQ_MagA0.mjs → src-0OJqEpGA.mjs} +6 -6
- package/dist/{src-6KF7hZTe.cjs → src-G1hs2GLZ.cjs} +1 -1
- package/package.json +4 -4
package/dist/cli.cjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_src = require('./src-
|
|
2
|
+
const require_src = require('./src-G1hs2GLZ.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");
|
|
6
|
+
node_path = require_src.__toESM(node_path);
|
|
6
7
|
let node_fs = require("node:fs");
|
|
7
8
|
let liquidjs = require("liquidjs");
|
|
9
|
+
let node_child_process = require("node:child_process");
|
|
8
10
|
let commander = require("commander");
|
|
9
11
|
let __agimon_ai_foundation_process_registry = require("@agimon-ai/foundation-process-registry");
|
|
10
12
|
let __agimon_ai_foundation_port_registry = require("@agimon-ai/foundation-port-registry");
|
|
13
|
+
let node_url = require("node:url");
|
|
11
14
|
|
|
12
15
|
//#region src/utils/output.ts
|
|
13
16
|
function writeLine(message = "") {
|
|
@@ -138,21 +141,16 @@ const CONFIG_FILE_NAMES = [
|
|
|
138
141
|
"mcp-config.json"
|
|
139
142
|
];
|
|
140
143
|
const MCP_ENDPOINT_PATH = "/mcp";
|
|
141
|
-
const
|
|
142
|
-
const DEFAULT_HOST = "localhost";
|
|
144
|
+
const DEFAULT_HOST$1 = "localhost";
|
|
143
145
|
const TRANSPORT_TYPE_STDIO = "stdio";
|
|
144
146
|
const TRANSPORT_TYPE_HTTP = "http";
|
|
145
147
|
const TRANSPORT_TYPE_SSE = "sse";
|
|
146
148
|
const TRANSPORT_TYPE_STDIO_HTTP = "stdio-http";
|
|
147
149
|
const RUNTIME_TRANSPORT = TRANSPORT_TYPE_HTTP;
|
|
148
|
-
const STDIO_HTTP_PROXY_STOP_LABEL = "Failed stopping stdio-http proxy";
|
|
149
|
-
const INTERNAL_HTTP_STOP_LABEL = "Failed stopping internal HTTP transport";
|
|
150
150
|
const PORT_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
|
|
151
|
-
const PORT_REGISTRY_SERVICE_STDIO_HTTP = "mcp-proxy-stdio-http";
|
|
152
151
|
const PORT_REGISTRY_SERVICE_TYPE = "service";
|
|
153
152
|
const PORT_REGISTRY_REPOSITORY_PATH = process.cwd();
|
|
154
153
|
const PROCESS_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
|
|
155
|
-
const PROCESS_REGISTRY_SERVICE_STDIO_HTTP = "mcp-proxy-stdio-http";
|
|
156
154
|
const PROCESS_REGISTRY_SERVICE_TYPE = "service";
|
|
157
155
|
const PROCESS_REGISTRY_REPOSITORY_PATH = process.cwd();
|
|
158
156
|
function toErrorMessage$9(error) {
|
|
@@ -164,13 +162,6 @@ function isValidTransportType(type) {
|
|
|
164
162
|
function isValidProxyMode(mode) {
|
|
165
163
|
return mode === "meta" || mode === "flat" || mode === "search";
|
|
166
164
|
}
|
|
167
|
-
function isAddressInUseError(error) {
|
|
168
|
-
if (error instanceof Error && error.message.includes("EADDRINUSE")) return true;
|
|
169
|
-
if (typeof error !== "object" || error === null) return false;
|
|
170
|
-
if ("code" in error && error.code === "EADDRINUSE") return true;
|
|
171
|
-
if ("message" in error && typeof error.message === "string" && error.message.includes("EADDRINUSE")) return true;
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
165
|
async function pathExists(filePath) {
|
|
175
166
|
try {
|
|
176
167
|
await (0, node_fs_promises.access)(filePath, node_fs.constants.F_OK);
|
|
@@ -218,10 +209,11 @@ function validateProxyMode(mode) {
|
|
|
218
209
|
if (!isValidProxyMode(mode)) throw new Error(`Unknown proxy mode: '${mode}'. Valid options: meta, flat, search`);
|
|
219
210
|
}
|
|
220
211
|
function createTransportConfig(options, mode) {
|
|
212
|
+
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
221
213
|
return {
|
|
222
214
|
mode,
|
|
223
|
-
port: options.port
|
|
224
|
-
host: options.host || process.env.MCP_HOST || DEFAULT_HOST
|
|
215
|
+
port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0),
|
|
216
|
+
host: options.host || process.env.MCP_HOST || DEFAULT_HOST$1
|
|
225
217
|
};
|
|
226
218
|
}
|
|
227
219
|
function createServerOptions(options, resolvedConfigPath, serverId) {
|
|
@@ -237,12 +229,12 @@ function createServerOptions(options, resolvedConfigPath, serverId) {
|
|
|
237
229
|
function formatStartError(type, host, port, error) {
|
|
238
230
|
const startErrorMessage = toErrorMessage$9(error);
|
|
239
231
|
if (type === TRANSPORT_TYPE_STDIO) return `Failed to start MCP server with transport '${type}': ${startErrorMessage}`;
|
|
240
|
-
return `Failed to start MCP server with transport '${type}' on ${host}:${port}: ${startErrorMessage}`;
|
|
232
|
+
return `Failed to start MCP server with transport '${type}' on ${port === void 0 ? `${host} (dynamic port)` : `${host}:${port}`}: ${startErrorMessage}`;
|
|
241
233
|
}
|
|
242
234
|
function createRuntimeRecord(serverId, config, port, shutdownToken, configPath) {
|
|
243
235
|
return {
|
|
244
236
|
serverId,
|
|
245
|
-
host: config.host ?? DEFAULT_HOST,
|
|
237
|
+
host: config.host ?? DEFAULT_HOST$1,
|
|
246
238
|
port,
|
|
247
239
|
transport: RUNTIME_TRANSPORT,
|
|
248
240
|
shutdownToken,
|
|
@@ -260,7 +252,10 @@ function createProcessRegistryService() {
|
|
|
260
252
|
function getRegistryEnvironment() {
|
|
261
253
|
return process.env.NODE_ENV ?? "development";
|
|
262
254
|
}
|
|
263
|
-
async function createPortRegistryLease(serviceName, host, preferredPort, serverId, transport, configPath
|
|
255
|
+
async function createPortRegistryLease(serviceName, host, preferredPort, serverId, transport, configPath, portRange = preferredPort !== void 0 ? {
|
|
256
|
+
min: preferredPort,
|
|
257
|
+
max: preferredPort
|
|
258
|
+
} : __agimon_ai_foundation_port_registry.DEFAULT_PORT_RANGE) {
|
|
264
259
|
const portRegistry = createPortRegistryService();
|
|
265
260
|
const result = await portRegistry.reservePort({
|
|
266
261
|
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
@@ -270,10 +265,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
|
|
|
270
265
|
pid: process.pid,
|
|
271
266
|
host,
|
|
272
267
|
preferredPort,
|
|
273
|
-
portRange
|
|
274
|
-
min: preferredPort,
|
|
275
|
-
max: preferredPort
|
|
276
|
-
},
|
|
268
|
+
portRange,
|
|
277
269
|
force: true,
|
|
278
270
|
metadata: {
|
|
279
271
|
transport,
|
|
@@ -281,21 +273,27 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
|
|
|
281
273
|
...configPath ? { configPath } : {}
|
|
282
274
|
}
|
|
283
275
|
});
|
|
284
|
-
if (!result.success || !result.record)
|
|
276
|
+
if (!result.success || !result.record) {
|
|
277
|
+
const requestedPortLabel = preferredPort === void 0 ? "dynamic port" : `port ${preferredPort}`;
|
|
278
|
+
throw new Error(result.error || `Failed to reserve ${requestedPortLabel} in port registry`);
|
|
279
|
+
}
|
|
285
280
|
let released = false;
|
|
286
|
-
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
281
|
+
return {
|
|
282
|
+
port: result.record.port,
|
|
283
|
+
release: async () => {
|
|
284
|
+
if (released) return;
|
|
285
|
+
released = true;
|
|
286
|
+
const releaseResult = await portRegistry.releasePort({
|
|
287
|
+
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
288
|
+
serviceName,
|
|
289
|
+
serviceType: PORT_REGISTRY_SERVICE_TYPE,
|
|
290
|
+
pid: process.pid,
|
|
291
|
+
environment: getRegistryEnvironment(),
|
|
292
|
+
force: true
|
|
293
|
+
});
|
|
294
|
+
if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes("No matching registry entry")) throw new Error(releaseResult.error || `Failed to release port for ${serviceName}`);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
299
297
|
}
|
|
300
298
|
async function createProcessRegistryLease(serviceName, host, port, serverId, transport, configPath) {
|
|
301
299
|
const processRegistry = createProcessRegistryService();
|
|
@@ -387,27 +385,6 @@ async function cleanupFailedRuntimeStartup(handler, runtimeStateService, serverI
|
|
|
387
385
|
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
388
386
|
}
|
|
389
387
|
}
|
|
390
|
-
async function stopTransportWithContext(label, stopOperation) {
|
|
391
|
-
try {
|
|
392
|
-
await stopOperation();
|
|
393
|
-
} catch (error) {
|
|
394
|
-
throw new Error(`${label}: ${toErrorMessage$9(error)}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
async function removeRuntimeRecordDuringStop(runtimeStateService, serverId) {
|
|
398
|
-
try {
|
|
399
|
-
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
400
|
-
} catch (error) {
|
|
401
|
-
throw new Error(`Failed to remove runtime state during HTTP stop callback for '${serverId}': ${toErrorMessage$9(error)}`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
function createStdioHttpInternalTransport(sharedServices, config, adminOptions) {
|
|
405
|
-
try {
|
|
406
|
-
return new require_src.HttpTransportHandler(() => require_src.createSessionServer(sharedServices), config, adminOptions);
|
|
407
|
-
} catch (error) {
|
|
408
|
-
throw new Error(`Failed to create internal HTTP transport for stdio-http proxy: ${toErrorMessage$9(error)}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
388
|
/**
|
|
412
389
|
* Start MCP server with given transport handler
|
|
413
390
|
* @param handler - The transport handler to start
|
|
@@ -438,13 +415,23 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
438
415
|
const runtimeStateService = new require_src.RuntimeStateService();
|
|
439
416
|
const shutdownToken = (0, node_crypto.randomUUID)();
|
|
440
417
|
const runtimeServerId = serverOptions.serverId ?? require_src.generateServerId();
|
|
441
|
-
const
|
|
442
|
-
const
|
|
418
|
+
const requestedPort = config.port;
|
|
419
|
+
const portRange = requestedPort !== void 0 ? {
|
|
420
|
+
min: requestedPort,
|
|
421
|
+
max: requestedPort
|
|
422
|
+
} : __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);
|
|
424
|
+
const runtimePort = portLease.port;
|
|
425
|
+
const runtimeConfig = {
|
|
426
|
+
...config,
|
|
427
|
+
port: runtimePort
|
|
428
|
+
};
|
|
429
|
+
const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST$1, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
|
|
443
430
|
let releasePort = async () => {
|
|
444
|
-
await releasePortLease(portLease);
|
|
431
|
+
await releasePortLease(portLease ?? null);
|
|
445
432
|
releasePort = async () => void 0;
|
|
446
433
|
};
|
|
447
|
-
const runtimeRecord = createRuntimeRecord(runtimeServerId,
|
|
434
|
+
const runtimeRecord = createRuntimeRecord(runtimeServerId, runtimeConfig, runtimePort, shutdownToken, resolvedConfigPath);
|
|
448
435
|
let handler;
|
|
449
436
|
let isStopping = false;
|
|
450
437
|
const stopHandler = async () => {
|
|
@@ -460,7 +447,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
460
447
|
}
|
|
461
448
|
};
|
|
462
449
|
try {
|
|
463
|
-
handler = new require_src.HttpTransportHandler(() => require_src.createSessionServer(sharedServices),
|
|
450
|
+
handler = new require_src.HttpTransportHandler(() => require_src.createSessionServer(sharedServices), runtimeConfig, createHttpAdminOptions(runtimeRecord.serverId, shutdownToken, stopHandler));
|
|
464
451
|
} catch (error) {
|
|
465
452
|
await releasePort();
|
|
466
453
|
await processLease.release({
|
|
@@ -478,7 +465,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
478
465
|
kill: false,
|
|
479
466
|
releasePort: false
|
|
480
467
|
});
|
|
481
|
-
await
|
|
468
|
+
await removeRuntimeRecord(runtimeStateService, runtimeRecord.serverId);
|
|
482
469
|
});
|
|
483
470
|
await writeRuntimeRecord(runtimeStateService, runtimeRecord);
|
|
484
471
|
} catch (error) {
|
|
@@ -493,19 +480,6 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
493
480
|
}
|
|
494
481
|
console.error(`Runtime state: http://${runtimeRecord.host}:${runtimeRecord.port} (${runtimeRecord.serverId})`);
|
|
495
482
|
}
|
|
496
|
-
async function stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport) {
|
|
497
|
-
try {
|
|
498
|
-
const stopOperations = [stopTransportWithContext(STDIO_HTTP_PROXY_STOP_LABEL, async () => {
|
|
499
|
-
await stdioHttpHandler.stop();
|
|
500
|
-
})];
|
|
501
|
-
if (ownsInternalHttpTransport && httpHandler) stopOperations.push(stopTransportWithContext(INTERNAL_HTTP_STOP_LABEL, async () => {
|
|
502
|
-
await httpHandler.stop();
|
|
503
|
-
}));
|
|
504
|
-
await Promise.all(stopOperations);
|
|
505
|
-
} catch (error) {
|
|
506
|
-
throw new Error(`Failed to stop stdio-http transport: ${toErrorMessage$9(error)}`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
483
|
async function startStdioTransport(serverOptions) {
|
|
510
484
|
try {
|
|
511
485
|
await startServer(new require_src.StdioTransportHandler(await require_src.createServer(serverOptions)));
|
|
@@ -520,109 +494,21 @@ async function startSseTransport(serverOptions, config) {
|
|
|
520
494
|
throw new Error(`Failed to start SSE transport: ${toErrorMessage$9(error)}`);
|
|
521
495
|
}
|
|
522
496
|
}
|
|
523
|
-
async function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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}`);
|
|
499
|
+
const result = await createPortRegistryService().getPort({
|
|
500
|
+
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
501
|
+
serviceName: PORT_REGISTRY_SERVICE_HTTP,
|
|
502
|
+
serviceType: PORT_REGISTRY_SERVICE_TYPE,
|
|
503
|
+
environment: getRegistryEnvironment()
|
|
504
|
+
});
|
|
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}`);
|
|
507
|
+
}
|
|
508
|
+
async function startStdioHttpTransport(config) {
|
|
527
509
|
try {
|
|
528
|
-
|
|
529
|
-
const runtimeStateService = new require_src.RuntimeStateService();
|
|
530
|
-
const serverId = serverOptions.serverId ?? require_src.generateServerId();
|
|
531
|
-
const shutdownToken = (0, node_crypto.randomUUID)();
|
|
532
|
-
processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_STDIO_HTTP, config.host ?? DEFAULT_HOST, config.port ?? DEFAULT_PORT, serverId, TRANSPORT_TYPE_STDIO_HTTP, resolvedConfigPath);
|
|
533
|
-
let httpHandler = null;
|
|
534
|
-
let ownsInternalHttpTransport = false;
|
|
535
|
-
let isStopping = false;
|
|
536
|
-
let releaseInternalPort = async () => {
|
|
537
|
-
await releasePortLease(internalPortLease);
|
|
538
|
-
internalPortLease = null;
|
|
539
|
-
releaseInternalPort = async () => void 0;
|
|
540
|
-
};
|
|
541
|
-
const stopOwnedRuntime = async () => {
|
|
542
|
-
if (isStopping) return;
|
|
543
|
-
isStopping = true;
|
|
544
|
-
try {
|
|
545
|
-
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
546
|
-
await releaseInternalPort();
|
|
547
|
-
if (shared.services) await shared.services.dispose();
|
|
548
|
-
ownsInternalHttpTransport = false;
|
|
549
|
-
process.exit(0);
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error(`Unexpected error during admin shutdown: ${toErrorMessage$9(error)}`);
|
|
552
|
-
process.exit(1);
|
|
553
|
-
}
|
|
554
|
-
};
|
|
555
|
-
const adminOptions = createHttpAdminOptions(serverId, shutdownToken, stopOwnedRuntime);
|
|
556
|
-
await startServer({
|
|
557
|
-
async start() {
|
|
558
|
-
let initialProxyConnectError;
|
|
559
|
-
try {
|
|
560
|
-
await stdioHttpHandler.start();
|
|
561
|
-
return;
|
|
562
|
-
} catch (error) {
|
|
563
|
-
initialProxyConnectError = error;
|
|
564
|
-
}
|
|
565
|
-
if (!shared.services) shared.services = await require_src.initializeSharedServices(serverOptions);
|
|
566
|
-
try {
|
|
567
|
-
internalPortLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_STDIO_HTTP, config.host ?? DEFAULT_HOST, config.port ?? DEFAULT_PORT, serverId, TRANSPORT_TYPE_STDIO_HTTP, resolvedConfigPath);
|
|
568
|
-
httpHandler = createStdioHttpInternalTransport(shared.services, config, adminOptions);
|
|
569
|
-
await httpHandler.start();
|
|
570
|
-
ownsInternalHttpTransport = true;
|
|
571
|
-
} catch (error) {
|
|
572
|
-
if (!isAddressInUseError(error)) {
|
|
573
|
-
await releaseInternalPort();
|
|
574
|
-
throw new Error(`Failed to start internal HTTP transport for stdio-http proxy: ${toErrorMessage$9(error)}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
try {
|
|
578
|
-
await stdioHttpHandler.start();
|
|
579
|
-
} catch (error) {
|
|
580
|
-
let rollbackStopErrorMessage = "";
|
|
581
|
-
if (ownsInternalHttpTransport && httpHandler) {
|
|
582
|
-
try {
|
|
583
|
-
await httpHandler.stop();
|
|
584
|
-
} catch (stopError) {
|
|
585
|
-
rollbackStopErrorMessage = toErrorMessage$9(stopError);
|
|
586
|
-
}
|
|
587
|
-
ownsInternalHttpTransport = false;
|
|
588
|
-
}
|
|
589
|
-
await releaseInternalPort();
|
|
590
|
-
const retryErrorMessage = toErrorMessage$9(error);
|
|
591
|
-
const initialErrorMessage = toErrorMessage$9(initialProxyConnectError);
|
|
592
|
-
const rollbackMessage = rollbackStopErrorMessage ? `; rollback stop failed: ${rollbackStopErrorMessage}` : "";
|
|
593
|
-
throw new Error(`Failed to start stdio-http proxy bridge: initial connect failed (${initialErrorMessage}); retry failed (${retryErrorMessage})${rollbackMessage}`);
|
|
594
|
-
}
|
|
595
|
-
if (ownsInternalHttpTransport) try {
|
|
596
|
-
await writeRuntimeRecord(runtimeStateService, createRuntimeRecord(serverId, config, config.port ?? DEFAULT_PORT, shutdownToken, resolvedConfigPath));
|
|
597
|
-
} catch (error) {
|
|
598
|
-
throw new Error(`Failed to persist runtime state for stdio-http server '${serverId}': ${toErrorMessage$9(error)}`);
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
async stop() {
|
|
602
|
-
try {
|
|
603
|
-
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
604
|
-
await releaseInternalPort();
|
|
605
|
-
await processLease?.release({
|
|
606
|
-
kill: false,
|
|
607
|
-
releasePort: false
|
|
608
|
-
});
|
|
609
|
-
ownsInternalHttpTransport = false;
|
|
610
|
-
if (shared.services) await shared.services.dispose();
|
|
611
|
-
} catch (error) {
|
|
612
|
-
ownsInternalHttpTransport = false;
|
|
613
|
-
throw new Error(`Failed during stdio-http shutdown for '${serverId}': ${toErrorMessage$9(error)}`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
});
|
|
510
|
+
await startServer(new require_src.StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config) }));
|
|
617
511
|
} catch (error) {
|
|
618
|
-
try {
|
|
619
|
-
await releasePortLease(internalPortLease);
|
|
620
|
-
await processLease?.release({
|
|
621
|
-
kill: false,
|
|
622
|
-
releasePort: false
|
|
623
|
-
});
|
|
624
|
-
} catch {}
|
|
625
|
-
if (shared.services) await shared.services.dispose();
|
|
626
512
|
throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$9(error)}`);
|
|
627
513
|
}
|
|
628
514
|
}
|
|
@@ -640,7 +526,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
|
|
|
640
526
|
await startSseTransport(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.SSE));
|
|
641
527
|
return;
|
|
642
528
|
}
|
|
643
|
-
await startStdioHttpTransport(
|
|
529
|
+
await startStdioHttpTransport(createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP));
|
|
644
530
|
} catch (error) {
|
|
645
531
|
throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$9(error)}`);
|
|
646
532
|
}
|
|
@@ -648,7 +534,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
|
|
|
648
534
|
/**
|
|
649
535
|
* MCP Serve command
|
|
650
536
|
*/
|
|
651
|
-
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
|
|
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) => {
|
|
652
538
|
try {
|
|
653
539
|
const transportType = validateTransportType(options.type.toLowerCase());
|
|
654
540
|
validateProxyMode(options.proxyMode);
|
|
@@ -657,11 +543,163 @@ const mcpServeCommand = new commander.Command("mcp-serve").description("Start MC
|
|
|
657
543
|
} catch (error) {
|
|
658
544
|
const rawTransportType = options.type.toLowerCase();
|
|
659
545
|
const transportType = isValidTransportType(rawTransportType) ? rawTransportType : TRANSPORT_TYPE_STDIO;
|
|
660
|
-
|
|
546
|
+
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
547
|
+
const requestedPort = options.port ?? (Number.isFinite(envPort) ? envPort : void 0);
|
|
548
|
+
console.error(formatStartError(transportType, options.host, requestedPort, error));
|
|
661
549
|
process.exit(1);
|
|
662
550
|
}
|
|
663
551
|
});
|
|
664
552
|
|
|
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
|
+
|
|
665
703
|
//#endregion
|
|
666
704
|
//#region src/commands/bootstrap.ts
|
|
667
705
|
function toErrorMessage$8(error) {
|
|
@@ -1490,6 +1528,7 @@ async function main() {
|
|
|
1490
1528
|
program.name("mcp-proxy").description("MCP proxy server package").version(require_src.version);
|
|
1491
1529
|
program.addCommand(initCommand);
|
|
1492
1530
|
program.addCommand(mcpServeCommand);
|
|
1531
|
+
program.addCommand(prestartHttpCommand);
|
|
1493
1532
|
program.addCommand(searchToolsCommand);
|
|
1494
1533
|
program.addCommand(describeToolsCommand);
|
|
1495
1534
|
program.addCommand(useToolCommand);
|
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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-
|
|
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";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { access, writeFile } from "node:fs/promises";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
import { constants } from "node:fs";
|
|
5
|
+
import path, { join, resolve } from "node:path";
|
|
6
|
+
import { constants, existsSync } from "node:fs";
|
|
7
7
|
import { Liquid } from "liquidjs";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
8
9
|
import { Command } from "commander";
|
|
9
10
|
import { ProcessRegistryService } from "@agimon-ai/foundation-process-registry";
|
|
10
|
-
import { PortRegistryService } from "@agimon-ai/foundation-port-registry";
|
|
11
|
+
import { DEFAULT_PORT_RANGE, PortRegistryService } from "@agimon-ai/foundation-port-registry";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
11
13
|
|
|
12
14
|
//#region src/utils/output.ts
|
|
13
15
|
function writeLine(message = "") {
|
|
@@ -138,21 +140,16 @@ const CONFIG_FILE_NAMES = [
|
|
|
138
140
|
"mcp-config.json"
|
|
139
141
|
];
|
|
140
142
|
const MCP_ENDPOINT_PATH = "/mcp";
|
|
141
|
-
const
|
|
142
|
-
const DEFAULT_HOST = "localhost";
|
|
143
|
+
const DEFAULT_HOST$1 = "localhost";
|
|
143
144
|
const TRANSPORT_TYPE_STDIO = "stdio";
|
|
144
145
|
const TRANSPORT_TYPE_HTTP = "http";
|
|
145
146
|
const TRANSPORT_TYPE_SSE = "sse";
|
|
146
147
|
const TRANSPORT_TYPE_STDIO_HTTP = "stdio-http";
|
|
147
148
|
const RUNTIME_TRANSPORT = TRANSPORT_TYPE_HTTP;
|
|
148
|
-
const STDIO_HTTP_PROXY_STOP_LABEL = "Failed stopping stdio-http proxy";
|
|
149
|
-
const INTERNAL_HTTP_STOP_LABEL = "Failed stopping internal HTTP transport";
|
|
150
149
|
const PORT_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
|
|
151
|
-
const PORT_REGISTRY_SERVICE_STDIO_HTTP = "mcp-proxy-stdio-http";
|
|
152
150
|
const PORT_REGISTRY_SERVICE_TYPE = "service";
|
|
153
151
|
const PORT_REGISTRY_REPOSITORY_PATH = process.cwd();
|
|
154
152
|
const PROCESS_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
|
|
155
|
-
const PROCESS_REGISTRY_SERVICE_STDIO_HTTP = "mcp-proxy-stdio-http";
|
|
156
153
|
const PROCESS_REGISTRY_SERVICE_TYPE = "service";
|
|
157
154
|
const PROCESS_REGISTRY_REPOSITORY_PATH = process.cwd();
|
|
158
155
|
function toErrorMessage$9(error) {
|
|
@@ -164,13 +161,6 @@ function isValidTransportType(type) {
|
|
|
164
161
|
function isValidProxyMode(mode) {
|
|
165
162
|
return mode === "meta" || mode === "flat" || mode === "search";
|
|
166
163
|
}
|
|
167
|
-
function isAddressInUseError(error) {
|
|
168
|
-
if (error instanceof Error && error.message.includes("EADDRINUSE")) return true;
|
|
169
|
-
if (typeof error !== "object" || error === null) return false;
|
|
170
|
-
if ("code" in error && error.code === "EADDRINUSE") return true;
|
|
171
|
-
if ("message" in error && typeof error.message === "string" && error.message.includes("EADDRINUSE")) return true;
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
164
|
async function pathExists(filePath) {
|
|
175
165
|
try {
|
|
176
166
|
await access(filePath, constants.F_OK);
|
|
@@ -218,10 +208,11 @@ function validateProxyMode(mode) {
|
|
|
218
208
|
if (!isValidProxyMode(mode)) throw new Error(`Unknown proxy mode: '${mode}'. Valid options: meta, flat, search`);
|
|
219
209
|
}
|
|
220
210
|
function createTransportConfig(options, mode) {
|
|
211
|
+
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
221
212
|
return {
|
|
222
213
|
mode,
|
|
223
|
-
port: options.port
|
|
224
|
-
host: options.host || process.env.MCP_HOST || DEFAULT_HOST
|
|
214
|
+
port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0),
|
|
215
|
+
host: options.host || process.env.MCP_HOST || DEFAULT_HOST$1
|
|
225
216
|
};
|
|
226
217
|
}
|
|
227
218
|
function createServerOptions(options, resolvedConfigPath, serverId) {
|
|
@@ -237,12 +228,12 @@ function createServerOptions(options, resolvedConfigPath, serverId) {
|
|
|
237
228
|
function formatStartError(type, host, port, error) {
|
|
238
229
|
const startErrorMessage = toErrorMessage$9(error);
|
|
239
230
|
if (type === TRANSPORT_TYPE_STDIO) return `Failed to start MCP server with transport '${type}': ${startErrorMessage}`;
|
|
240
|
-
return `Failed to start MCP server with transport '${type}' on ${host}:${port}: ${startErrorMessage}`;
|
|
231
|
+
return `Failed to start MCP server with transport '${type}' on ${port === void 0 ? `${host} (dynamic port)` : `${host}:${port}`}: ${startErrorMessage}`;
|
|
241
232
|
}
|
|
242
233
|
function createRuntimeRecord(serverId, config, port, shutdownToken, configPath) {
|
|
243
234
|
return {
|
|
244
235
|
serverId,
|
|
245
|
-
host: config.host ?? DEFAULT_HOST,
|
|
236
|
+
host: config.host ?? DEFAULT_HOST$1,
|
|
246
237
|
port,
|
|
247
238
|
transport: RUNTIME_TRANSPORT,
|
|
248
239
|
shutdownToken,
|
|
@@ -260,7 +251,10 @@ function createProcessRegistryService() {
|
|
|
260
251
|
function getRegistryEnvironment() {
|
|
261
252
|
return process.env.NODE_ENV ?? "development";
|
|
262
253
|
}
|
|
263
|
-
async function createPortRegistryLease(serviceName, host, preferredPort, serverId, transport, configPath
|
|
254
|
+
async function createPortRegistryLease(serviceName, host, preferredPort, serverId, transport, configPath, portRange = preferredPort !== void 0 ? {
|
|
255
|
+
min: preferredPort,
|
|
256
|
+
max: preferredPort
|
|
257
|
+
} : DEFAULT_PORT_RANGE) {
|
|
264
258
|
const portRegistry = createPortRegistryService();
|
|
265
259
|
const result = await portRegistry.reservePort({
|
|
266
260
|
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
@@ -270,10 +264,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
|
|
|
270
264
|
pid: process.pid,
|
|
271
265
|
host,
|
|
272
266
|
preferredPort,
|
|
273
|
-
portRange
|
|
274
|
-
min: preferredPort,
|
|
275
|
-
max: preferredPort
|
|
276
|
-
},
|
|
267
|
+
portRange,
|
|
277
268
|
force: true,
|
|
278
269
|
metadata: {
|
|
279
270
|
transport,
|
|
@@ -281,21 +272,27 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
|
|
|
281
272
|
...configPath ? { configPath } : {}
|
|
282
273
|
}
|
|
283
274
|
});
|
|
284
|
-
if (!result.success || !result.record)
|
|
275
|
+
if (!result.success || !result.record) {
|
|
276
|
+
const requestedPortLabel = preferredPort === void 0 ? "dynamic port" : `port ${preferredPort}`;
|
|
277
|
+
throw new Error(result.error || `Failed to reserve ${requestedPortLabel} in port registry`);
|
|
278
|
+
}
|
|
285
279
|
let released = false;
|
|
286
|
-
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
280
|
+
return {
|
|
281
|
+
port: result.record.port,
|
|
282
|
+
release: async () => {
|
|
283
|
+
if (released) return;
|
|
284
|
+
released = true;
|
|
285
|
+
const releaseResult = await portRegistry.releasePort({
|
|
286
|
+
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
287
|
+
serviceName,
|
|
288
|
+
serviceType: PORT_REGISTRY_SERVICE_TYPE,
|
|
289
|
+
pid: process.pid,
|
|
290
|
+
environment: getRegistryEnvironment(),
|
|
291
|
+
force: true
|
|
292
|
+
});
|
|
293
|
+
if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes("No matching registry entry")) throw new Error(releaseResult.error || `Failed to release port for ${serviceName}`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
299
296
|
}
|
|
300
297
|
async function createProcessRegistryLease(serviceName, host, port, serverId, transport, configPath) {
|
|
301
298
|
const processRegistry = createProcessRegistryService();
|
|
@@ -387,27 +384,6 @@ async function cleanupFailedRuntimeStartup(handler, runtimeStateService, serverI
|
|
|
387
384
|
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
388
385
|
}
|
|
389
386
|
}
|
|
390
|
-
async function stopTransportWithContext(label, stopOperation) {
|
|
391
|
-
try {
|
|
392
|
-
await stopOperation();
|
|
393
|
-
} catch (error) {
|
|
394
|
-
throw new Error(`${label}: ${toErrorMessage$9(error)}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
async function removeRuntimeRecordDuringStop(runtimeStateService, serverId) {
|
|
398
|
-
try {
|
|
399
|
-
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
400
|
-
} catch (error) {
|
|
401
|
-
throw new Error(`Failed to remove runtime state during HTTP stop callback for '${serverId}': ${toErrorMessage$9(error)}`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
function createStdioHttpInternalTransport(sharedServices, config, adminOptions) {
|
|
405
|
-
try {
|
|
406
|
-
return new HttpTransportHandler(() => createSessionServer(sharedServices), config, adminOptions);
|
|
407
|
-
} catch (error) {
|
|
408
|
-
throw new Error(`Failed to create internal HTTP transport for stdio-http proxy: ${toErrorMessage$9(error)}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
387
|
/**
|
|
412
388
|
* Start MCP server with given transport handler
|
|
413
389
|
* @param handler - The transport handler to start
|
|
@@ -438,13 +414,23 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
438
414
|
const runtimeStateService = new RuntimeStateService();
|
|
439
415
|
const shutdownToken = randomUUID();
|
|
440
416
|
const runtimeServerId = serverOptions.serverId ?? generateServerId();
|
|
441
|
-
const
|
|
442
|
-
const
|
|
417
|
+
const requestedPort = config.port;
|
|
418
|
+
const portRange = requestedPort !== void 0 ? {
|
|
419
|
+
min: requestedPort,
|
|
420
|
+
max: requestedPort
|
|
421
|
+
} : DEFAULT_PORT_RANGE;
|
|
422
|
+
const portLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_HTTP, config.host ?? DEFAULT_HOST$1, requestedPort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath, portRange);
|
|
423
|
+
const runtimePort = portLease.port;
|
|
424
|
+
const runtimeConfig = {
|
|
425
|
+
...config,
|
|
426
|
+
port: runtimePort
|
|
427
|
+
};
|
|
428
|
+
const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST$1, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
|
|
443
429
|
let releasePort = async () => {
|
|
444
|
-
await releasePortLease(portLease);
|
|
430
|
+
await releasePortLease(portLease ?? null);
|
|
445
431
|
releasePort = async () => void 0;
|
|
446
432
|
};
|
|
447
|
-
const runtimeRecord = createRuntimeRecord(runtimeServerId,
|
|
433
|
+
const runtimeRecord = createRuntimeRecord(runtimeServerId, runtimeConfig, runtimePort, shutdownToken, resolvedConfigPath);
|
|
448
434
|
let handler;
|
|
449
435
|
let isStopping = false;
|
|
450
436
|
const stopHandler = async () => {
|
|
@@ -460,7 +446,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
460
446
|
}
|
|
461
447
|
};
|
|
462
448
|
try {
|
|
463
|
-
handler = new HttpTransportHandler(() => createSessionServer(sharedServices),
|
|
449
|
+
handler = new HttpTransportHandler(() => createSessionServer(sharedServices), runtimeConfig, createHttpAdminOptions(runtimeRecord.serverId, shutdownToken, stopHandler));
|
|
464
450
|
} catch (error) {
|
|
465
451
|
await releasePort();
|
|
466
452
|
await processLease.release({
|
|
@@ -478,7 +464,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
478
464
|
kill: false,
|
|
479
465
|
releasePort: false
|
|
480
466
|
});
|
|
481
|
-
await
|
|
467
|
+
await removeRuntimeRecord(runtimeStateService, runtimeRecord.serverId);
|
|
482
468
|
});
|
|
483
469
|
await writeRuntimeRecord(runtimeStateService, runtimeRecord);
|
|
484
470
|
} catch (error) {
|
|
@@ -493,19 +479,6 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
493
479
|
}
|
|
494
480
|
console.error(`Runtime state: http://${runtimeRecord.host}:${runtimeRecord.port} (${runtimeRecord.serverId})`);
|
|
495
481
|
}
|
|
496
|
-
async function stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport) {
|
|
497
|
-
try {
|
|
498
|
-
const stopOperations = [stopTransportWithContext(STDIO_HTTP_PROXY_STOP_LABEL, async () => {
|
|
499
|
-
await stdioHttpHandler.stop();
|
|
500
|
-
})];
|
|
501
|
-
if (ownsInternalHttpTransport && httpHandler) stopOperations.push(stopTransportWithContext(INTERNAL_HTTP_STOP_LABEL, async () => {
|
|
502
|
-
await httpHandler.stop();
|
|
503
|
-
}));
|
|
504
|
-
await Promise.all(stopOperations);
|
|
505
|
-
} catch (error) {
|
|
506
|
-
throw new Error(`Failed to stop stdio-http transport: ${toErrorMessage$9(error)}`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
482
|
async function startStdioTransport(serverOptions) {
|
|
510
483
|
try {
|
|
511
484
|
await startServer(new StdioTransportHandler(await createServer(serverOptions)));
|
|
@@ -520,109 +493,21 @@ async function startSseTransport(serverOptions, config) {
|
|
|
520
493
|
throw new Error(`Failed to start SSE transport: ${toErrorMessage$9(error)}`);
|
|
521
494
|
}
|
|
522
495
|
}
|
|
523
|
-
async function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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}`);
|
|
498
|
+
const result = await createPortRegistryService().getPort({
|
|
499
|
+
repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
|
|
500
|
+
serviceName: PORT_REGISTRY_SERVICE_HTTP,
|
|
501
|
+
serviceType: PORT_REGISTRY_SERVICE_TYPE,
|
|
502
|
+
environment: getRegistryEnvironment()
|
|
503
|
+
});
|
|
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}`);
|
|
506
|
+
}
|
|
507
|
+
async function startStdioHttpTransport(config) {
|
|
527
508
|
try {
|
|
528
|
-
|
|
529
|
-
const runtimeStateService = new RuntimeStateService();
|
|
530
|
-
const serverId = serverOptions.serverId ?? generateServerId();
|
|
531
|
-
const shutdownToken = randomUUID();
|
|
532
|
-
processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_STDIO_HTTP, config.host ?? DEFAULT_HOST, config.port ?? DEFAULT_PORT, serverId, TRANSPORT_TYPE_STDIO_HTTP, resolvedConfigPath);
|
|
533
|
-
let httpHandler = null;
|
|
534
|
-
let ownsInternalHttpTransport = false;
|
|
535
|
-
let isStopping = false;
|
|
536
|
-
let releaseInternalPort = async () => {
|
|
537
|
-
await releasePortLease(internalPortLease);
|
|
538
|
-
internalPortLease = null;
|
|
539
|
-
releaseInternalPort = async () => void 0;
|
|
540
|
-
};
|
|
541
|
-
const stopOwnedRuntime = async () => {
|
|
542
|
-
if (isStopping) return;
|
|
543
|
-
isStopping = true;
|
|
544
|
-
try {
|
|
545
|
-
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
546
|
-
await releaseInternalPort();
|
|
547
|
-
if (shared.services) await shared.services.dispose();
|
|
548
|
-
ownsInternalHttpTransport = false;
|
|
549
|
-
process.exit(0);
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error(`Unexpected error during admin shutdown: ${toErrorMessage$9(error)}`);
|
|
552
|
-
process.exit(1);
|
|
553
|
-
}
|
|
554
|
-
};
|
|
555
|
-
const adminOptions = createHttpAdminOptions(serverId, shutdownToken, stopOwnedRuntime);
|
|
556
|
-
await startServer({
|
|
557
|
-
async start() {
|
|
558
|
-
let initialProxyConnectError;
|
|
559
|
-
try {
|
|
560
|
-
await stdioHttpHandler.start();
|
|
561
|
-
return;
|
|
562
|
-
} catch (error) {
|
|
563
|
-
initialProxyConnectError = error;
|
|
564
|
-
}
|
|
565
|
-
if (!shared.services) shared.services = await initializeSharedServices(serverOptions);
|
|
566
|
-
try {
|
|
567
|
-
internalPortLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_STDIO_HTTP, config.host ?? DEFAULT_HOST, config.port ?? DEFAULT_PORT, serverId, TRANSPORT_TYPE_STDIO_HTTP, resolvedConfigPath);
|
|
568
|
-
httpHandler = createStdioHttpInternalTransport(shared.services, config, adminOptions);
|
|
569
|
-
await httpHandler.start();
|
|
570
|
-
ownsInternalHttpTransport = true;
|
|
571
|
-
} catch (error) {
|
|
572
|
-
if (!isAddressInUseError(error)) {
|
|
573
|
-
await releaseInternalPort();
|
|
574
|
-
throw new Error(`Failed to start internal HTTP transport for stdio-http proxy: ${toErrorMessage$9(error)}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
try {
|
|
578
|
-
await stdioHttpHandler.start();
|
|
579
|
-
} catch (error) {
|
|
580
|
-
let rollbackStopErrorMessage = "";
|
|
581
|
-
if (ownsInternalHttpTransport && httpHandler) {
|
|
582
|
-
try {
|
|
583
|
-
await httpHandler.stop();
|
|
584
|
-
} catch (stopError) {
|
|
585
|
-
rollbackStopErrorMessage = toErrorMessage$9(stopError);
|
|
586
|
-
}
|
|
587
|
-
ownsInternalHttpTransport = false;
|
|
588
|
-
}
|
|
589
|
-
await releaseInternalPort();
|
|
590
|
-
const retryErrorMessage = toErrorMessage$9(error);
|
|
591
|
-
const initialErrorMessage = toErrorMessage$9(initialProxyConnectError);
|
|
592
|
-
const rollbackMessage = rollbackStopErrorMessage ? `; rollback stop failed: ${rollbackStopErrorMessage}` : "";
|
|
593
|
-
throw new Error(`Failed to start stdio-http proxy bridge: initial connect failed (${initialErrorMessage}); retry failed (${retryErrorMessage})${rollbackMessage}`);
|
|
594
|
-
}
|
|
595
|
-
if (ownsInternalHttpTransport) try {
|
|
596
|
-
await writeRuntimeRecord(runtimeStateService, createRuntimeRecord(serverId, config, config.port ?? DEFAULT_PORT, shutdownToken, resolvedConfigPath));
|
|
597
|
-
} catch (error) {
|
|
598
|
-
throw new Error(`Failed to persist runtime state for stdio-http server '${serverId}': ${toErrorMessage$9(error)}`);
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
async stop() {
|
|
602
|
-
try {
|
|
603
|
-
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
604
|
-
await releaseInternalPort();
|
|
605
|
-
await processLease?.release({
|
|
606
|
-
kill: false,
|
|
607
|
-
releasePort: false
|
|
608
|
-
});
|
|
609
|
-
ownsInternalHttpTransport = false;
|
|
610
|
-
if (shared.services) await shared.services.dispose();
|
|
611
|
-
} catch (error) {
|
|
612
|
-
ownsInternalHttpTransport = false;
|
|
613
|
-
throw new Error(`Failed during stdio-http shutdown for '${serverId}': ${toErrorMessage$9(error)}`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
});
|
|
509
|
+
await startServer(new StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config) }));
|
|
617
510
|
} catch (error) {
|
|
618
|
-
try {
|
|
619
|
-
await releasePortLease(internalPortLease);
|
|
620
|
-
await processLease?.release({
|
|
621
|
-
kill: false,
|
|
622
|
-
releasePort: false
|
|
623
|
-
});
|
|
624
|
-
} catch {}
|
|
625
|
-
if (shared.services) await shared.services.dispose();
|
|
626
511
|
throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$9(error)}`);
|
|
627
512
|
}
|
|
628
513
|
}
|
|
@@ -640,7 +525,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
|
|
|
640
525
|
await startSseTransport(serverOptions, createTransportConfig(options, TRANSPORT_MODE.SSE));
|
|
641
526
|
return;
|
|
642
527
|
}
|
|
643
|
-
await startStdioHttpTransport(
|
|
528
|
+
await startStdioHttpTransport(createTransportConfig(options, TRANSPORT_MODE.HTTP));
|
|
644
529
|
} catch (error) {
|
|
645
530
|
throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$9(error)}`);
|
|
646
531
|
}
|
|
@@ -648,7 +533,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
|
|
|
648
533
|
/**
|
|
649
534
|
* MCP Serve command
|
|
650
535
|
*/
|
|
651
|
-
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
|
|
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) => {
|
|
652
537
|
try {
|
|
653
538
|
const transportType = validateTransportType(options.type.toLowerCase());
|
|
654
539
|
validateProxyMode(options.proxyMode);
|
|
@@ -657,11 +542,163 @@ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server w
|
|
|
657
542
|
} catch (error) {
|
|
658
543
|
const rawTransportType = options.type.toLowerCase();
|
|
659
544
|
const transportType = isValidTransportType(rawTransportType) ? rawTransportType : TRANSPORT_TYPE_STDIO;
|
|
660
|
-
|
|
545
|
+
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
546
|
+
const requestedPort = options.port ?? (Number.isFinite(envPort) ? envPort : void 0);
|
|
547
|
+
console.error(formatStartError(transportType, options.host, requestedPort, error));
|
|
661
548
|
process.exit(1);
|
|
662
549
|
}
|
|
663
550
|
});
|
|
664
551
|
|
|
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
|
+
|
|
665
702
|
//#endregion
|
|
666
703
|
//#region src/commands/bootstrap.ts
|
|
667
704
|
function toErrorMessage$8(error) {
|
|
@@ -1490,6 +1527,7 @@ async function main() {
|
|
|
1490
1527
|
program.name("mcp-proxy").description("MCP proxy server package").version(version);
|
|
1491
1528
|
program.addCommand(initCommand);
|
|
1492
1529
|
program.addCommand(mcpServeCommand);
|
|
1530
|
+
program.addCommand(prestartHttpCommand);
|
|
1493
1531
|
program.addCommand(searchToolsCommand);
|
|
1494
1532
|
program.addCommand(describeToolsCommand);
|
|
1495
1533
|
program.addCommand(useToolCommand);
|
package/dist/index.cjs
CHANGED
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-
|
|
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";
|
|
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 };
|
|
@@ -2758,9 +2758,9 @@ function isShutdownResponse(value) {
|
|
|
2758
2758
|
* @param path - Request path to append
|
|
2759
2759
|
* @returns Full runtime URL
|
|
2760
2760
|
*/
|
|
2761
|
-
function buildRuntimeUrl(runtime, path) {
|
|
2761
|
+
function buildRuntimeUrl(runtime, path$1) {
|
|
2762
2762
|
if (!ALLOWED_HOSTS.has(runtime.host)) throw new Error(`Refusing to connect to non-loopback host '${runtime.host}'. Only ${Array.from(ALLOWED_HOSTS).join(", ")} are allowed.`);
|
|
2763
|
-
return `${HTTP_PROTOCOL}${runtime.host}${URL_PORT_SEPARATOR}${runtime.port}${path}`;
|
|
2763
|
+
return `${HTTP_PROTOCOL}${runtime.host}${URL_PORT_SEPARATOR}${runtime.port}${path$1}`;
|
|
2764
2764
|
}
|
|
2765
2765
|
function toErrorMessage$1(error) {
|
|
2766
2766
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -2938,13 +2938,13 @@ var SkillLoadError = class extends Error {
|
|
|
2938
2938
|
* @returns true if path exists, false otherwise
|
|
2939
2939
|
* @throws Error for unexpected filesystem errors (permission denied, etc.)
|
|
2940
2940
|
*/
|
|
2941
|
-
async function pathExists(path) {
|
|
2941
|
+
async function pathExists(path$1) {
|
|
2942
2942
|
try {
|
|
2943
|
-
await access(path);
|
|
2943
|
+
await access(path$1);
|
|
2944
2944
|
return true;
|
|
2945
2945
|
} catch (error) {
|
|
2946
2946
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
2947
|
-
throw new Error(`Failed to check path existence for "${path}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2947
|
+
throw new Error(`Failed to check path existence for "${path$1}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2948
2948
|
}
|
|
2949
2949
|
}
|
|
2950
2950
|
/**
|
|
@@ -4313,7 +4313,7 @@ var StdioHttpTransportHandler = class {
|
|
|
4313
4313
|
|
|
4314
4314
|
//#endregion
|
|
4315
4315
|
//#region package.json
|
|
4316
|
-
var version = "0.
|
|
4316
|
+
var version = "0.4.2";
|
|
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
|
+
"version": "0.4.3",
|
|
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.
|
|
31
|
-
"@agimon-ai/foundation-port-registry": "0.2.
|
|
32
|
-
"@agimon-ai/log-sink-mcp": "0.2.
|
|
30
|
+
"@agimon-ai/foundation-process-registry": "0.2.3",
|
|
31
|
+
"@agimon-ai/foundation-port-registry": "0.2.7",
|
|
32
|
+
"@agimon-ai/log-sink-mcp": "0.2.7"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/express": "^5.0.0",
|