@agimon-ai/mcp-proxy 0.4.11 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +146 -105
- package/dist/cli.mjs +146 -106
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +74 -60
- package/dist/index.d.mts +74 -60
- package/dist/index.mjs +1 -1
- package/dist/{src-DwErnAUn.cjs → src-B5N-kt9Y.cjs} +2965 -2802
- package/dist/{src-Cp1GdSlN.mjs → src-DQSfFKFP.mjs} +2968 -2804
- package/package.json +9 -6
package/dist/cli.cjs
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_src = require('./src-
|
|
3
|
-
let
|
|
2
|
+
const require_src = require('./src-B5N-kt9Y.cjs');
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
4
|
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let js_yaml = require("js-yaml");
|
|
6
|
+
js_yaml = require_src.__toESM(js_yaml);
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
5
8
|
let node_path = require("node:path");
|
|
6
9
|
node_path = require_src.__toESM(node_path);
|
|
7
|
-
let node_fs = require("node:fs");
|
|
8
|
-
let liquidjs = require("liquidjs");
|
|
9
10
|
let node_child_process = require("node:child_process");
|
|
11
|
+
let liquidjs = require("liquidjs");
|
|
10
12
|
let commander = require("commander");
|
|
11
13
|
let __agimon_ai_foundation_port_registry = require("@agimon-ai/foundation-port-registry");
|
|
12
14
|
let __agimon_ai_foundation_process_registry = require("@agimon-ai/foundation-process-registry");
|
|
13
15
|
let node_url = require("node:url");
|
|
14
16
|
|
|
17
|
+
//#region src/templates/mcp-config.json?raw
|
|
18
|
+
var mcp_config_default = "{\n \"_comment\": \"MCP Server Configuration - Use ${VAR_NAME} syntax for environment variable interpolation\",\n \"_instructions\": \"config.instruction: Server's default instruction | instruction: User override (takes precedence)\",\n \"mcpServers\": {\n \"example-server\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/mcp-server/build/index.js\"],\n \"env\": {\n \"LOG_LEVEL\": \"info\",\n \"_comment\": \"You can use environment variable interpolation:\",\n \"_example_DATABASE_URL\": \"${DATABASE_URL}\",\n \"_example_API_KEY\": \"${MY_API_KEY}\"\n },\n \"config\": {\n \"instruction\": \"Use this server for...\"\n },\n \"_instruction_override\": \"Optional user override - takes precedence over config.instruction\"\n }\n }\n}\n";
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/templates/mcp-config.yaml.liquid?raw
|
|
22
|
+
var mcp_config_yaml_default = "# MCP Server Configuration\n# This file configures the MCP servers that mcp-proxy will connect to\n#\n# Environment Variable Interpolation:\n# Use ${VAR_NAME} syntax to reference environment variables\n# Example: ${HOME}, ${API_KEY}, ${DATABASE_URL}\n#\n# Instructions:\n# - config.instruction: Server's default instruction (from server documentation)\n# - instruction: User override (optional, takes precedence over config.instruction)\n# - config.toolBlacklist: Array of tool names to hide/block from this server\n# - config.omitToolDescription: Boolean to show only tool names without descriptions (saves tokens)\n\n# Remote Configuration Sources (OPTIONAL)\n# Fetch and merge configurations from remote URLs\n# Remote configs are merged with local configs based on merge strategy\n#\n# SECURITY: SSRF Protection is ENABLED by default\n# - Only HTTPS URLs are allowed (set security.enforceHttps: false to allow HTTP)\n# - Private IPs and localhost are blocked (set security.allowPrivateIPs: true for internal networks)\n# - Blocked ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16\nremoteConfigs:\n # Example 1: Basic remote config with default security\n # - url: ${AGIFLOW_URL}/api/v1/mcp-configs\n # headers:\n # Authorization: Bearer ${AGIFLOW_API_KEY}\n # mergeStrategy: local-priority # Options: local-priority (default), remote-priority, merge-deep\n #\n # Example 2: Remote config with custom security settings (for internal networks)\n # - url: ${INTERNAL_URL}/mcp-configs\n # headers:\n # Authorization: Bearer ${INTERNAL_TOKEN}\n # security:\n # allowPrivateIPs: true # Allow internal IPs (default: false)\n # enforceHttps: false # Allow HTTP (default: true, HTTPS only)\n # mergeStrategy: local-priority\n #\n # Example 3: Remote config with additional validation (OPTIONAL)\n # - url: ${AGIFLOW_URL}/api/v1/mcp-configs\n # headers:\n # Authorization: Bearer ${AGIFLOW_API_KEY}\n # X-API-Key: ${AGIFLOW_API_KEY}\n # security:\n # enforceHttps: true # Require HTTPS (default: true)\n # allowPrivateIPs: false # Block private IPs (default: false)\n # validation: # OPTIONAL: Additional regex validation on top of security checks\n # url: ^https://.*\\.agiflow\\.io/.* # OPTIONAL: Regex pattern to validate URL format\n # headers: # OPTIONAL: Regex patterns to validate header values\n # Authorization: ^Bearer [A-Za-z0-9_-]+$\n # X-API-Key: ^[A-Za-z0-9_-]{32,}$\n # mergeStrategy: local-priority\n\nmcpServers:\n{%- if mcpServers %}{% for server in mcpServers %}\n {{ server.name }}:\n command: {{ server.command }}\n args:{% for arg in server.args %}\n - '{{ arg }}'{% endfor %}\n # env:\n # LOG_LEVEL: info\n # # API_KEY: ${MY_API_KEY}\n # config:\n # instruction: Use this server for...\n # # toolBlacklist:\n # # - tool_to_block\n # # omitToolDescription: true\n{% endfor %}\n # Example MCP server using SSE transport\n # remote-server:\n # url: https://example.com/mcp\n # type: sse\n # headers:\n # Authorization: Bearer ${API_KEY}\n # config:\n # instruction: This server provides tools for...\n{% else %}\n # Example MCP server using stdio transport\n example-server:\n command: node\n args:\n - /path/to/mcp-server/build/index.js\n env:\n # Environment variables for the MCP server\n LOG_LEVEL: info\n # You can use environment variable interpolation:\n # DATABASE_URL: ${DATABASE_URL}\n # API_KEY: ${MY_API_KEY}\n config:\n # Server's default instruction (from server documentation)\n instruction: Use this server for...\n # Optional: Block specific tools from being listed or executed\n # toolBlacklist:\n # - dangerous_tool_name\n # - another_blocked_tool\n # Optional: Omit tool descriptions to save tokens (default: false)\n # omitToolDescription: true\n # instruction: Optional user override - takes precedence over config.instruction\n\n # Example MCP server using SSE transport with environment variables\n # remote-server:\n # url: https://example.com/mcp\n # type: sse\n # headers:\n # # Use ${VAR_NAME} to interpolate environment variables\n # Authorization: Bearer ${API_KEY}\n # config:\n # instruction: This server provides tools for...\n # # Optional: Block specific tools from being listed or executed\n # # toolBlacklist:\n # # - tool_to_block\n # # Optional: Omit tool descriptions to save tokens (default: false)\n # # omitToolDescription: true\n # # instruction: Optional user override\n{% endif %}\n";
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
15
25
|
//#region src/utils/output.ts
|
|
16
26
|
function writeLine(message = "") {
|
|
17
27
|
console.log(message);
|
|
@@ -35,14 +45,6 @@ const print = {
|
|
|
35
45
|
indent: (message) => writeLine(` ${message}`)
|
|
36
46
|
};
|
|
37
47
|
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/templates/mcp-config.yaml.liquid?raw
|
|
40
|
-
var mcp_config_yaml_default = "# MCP Server Configuration\n# This file configures the MCP servers that mcp-proxy will connect to\n#\n# Environment Variable Interpolation:\n# Use ${VAR_NAME} syntax to reference environment variables\n# Example: ${HOME}, ${API_KEY}, ${DATABASE_URL}\n#\n# Instructions:\n# - config.instruction: Server's default instruction (from server documentation)\n# - instruction: User override (optional, takes precedence over config.instruction)\n# - config.toolBlacklist: Array of tool names to hide/block from this server\n# - config.omitToolDescription: Boolean to show only tool names without descriptions (saves tokens)\n\n# Remote Configuration Sources (OPTIONAL)\n# Fetch and merge configurations from remote URLs\n# Remote configs are merged with local configs based on merge strategy\n#\n# SECURITY: SSRF Protection is ENABLED by default\n# - Only HTTPS URLs are allowed (set security.enforceHttps: false to allow HTTP)\n# - Private IPs and localhost are blocked (set security.allowPrivateIPs: true for internal networks)\n# - Blocked ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16\nremoteConfigs:\n # Example 1: Basic remote config with default security\n # - url: ${AGIFLOW_URL}/api/v1/mcp-configs\n # headers:\n # Authorization: Bearer ${AGIFLOW_API_KEY}\n # mergeStrategy: local-priority # Options: local-priority (default), remote-priority, merge-deep\n #\n # Example 2: Remote config with custom security settings (for internal networks)\n # - url: ${INTERNAL_URL}/mcp-configs\n # headers:\n # Authorization: Bearer ${INTERNAL_TOKEN}\n # security:\n # allowPrivateIPs: true # Allow internal IPs (default: false)\n # enforceHttps: false # Allow HTTP (default: true, HTTPS only)\n # mergeStrategy: local-priority\n #\n # Example 3: Remote config with additional validation (OPTIONAL)\n # - url: ${AGIFLOW_URL}/api/v1/mcp-configs\n # headers:\n # Authorization: Bearer ${AGIFLOW_API_KEY}\n # X-API-Key: ${AGIFLOW_API_KEY}\n # security:\n # enforceHttps: true # Require HTTPS (default: true)\n # allowPrivateIPs: false # Block private IPs (default: false)\n # validation: # OPTIONAL: Additional regex validation on top of security checks\n # url: ^https://.*\\.agiflow\\.io/.* # OPTIONAL: Regex pattern to validate URL format\n # headers: # OPTIONAL: Regex patterns to validate header values\n # Authorization: ^Bearer [A-Za-z0-9_-]+$\n # X-API-Key: ^[A-Za-z0-9_-]{32,}$\n # mergeStrategy: local-priority\n\nmcpServers:\n{%- if mcpServers %}{% for server in mcpServers %}\n {{ server.name }}:\n command: {{ server.command }}\n args:{% for arg in server.args %}\n - '{{ arg }}'{% endfor %}\n # env:\n # LOG_LEVEL: info\n # # API_KEY: ${MY_API_KEY}\n # config:\n # instruction: Use this server for...\n # # toolBlacklist:\n # # - tool_to_block\n # # omitToolDescription: true\n{% endfor %}\n # Example MCP server using SSE transport\n # remote-server:\n # url: https://example.com/mcp\n # type: sse\n # headers:\n # Authorization: Bearer ${API_KEY}\n # config:\n # instruction: This server provides tools for...\n{% else %}\n # Example MCP server using stdio transport\n example-server:\n command: node\n args:\n - /path/to/mcp-server/build/index.js\n env:\n # Environment variables for the MCP server\n LOG_LEVEL: info\n # You can use environment variable interpolation:\n # DATABASE_URL: ${DATABASE_URL}\n # API_KEY: ${MY_API_KEY}\n config:\n # Server's default instruction (from server documentation)\n instruction: Use this server for...\n # Optional: Block specific tools from being listed or executed\n # toolBlacklist:\n # - dangerous_tool_name\n # - another_blocked_tool\n # Optional: Omit tool descriptions to save tokens (default: false)\n # omitToolDescription: true\n # instruction: Optional user override - takes precedence over config.instruction\n\n # Example MCP server using SSE transport with environment variables\n # remote-server:\n # url: https://example.com/mcp\n # type: sse\n # headers:\n # # Use ${VAR_NAME} to interpolate environment variables\n # Authorization: Bearer ${API_KEY}\n # config:\n # instruction: This server provides tools for...\n # # Optional: Block specific tools from being listed or executed\n # # toolBlacklist:\n # # - tool_to_block\n # # Optional: Omit tool descriptions to save tokens (default: false)\n # # omitToolDescription: true\n # # instruction: Optional user override\n{% endif %}\n";
|
|
41
|
-
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/templates/mcp-config.json?raw
|
|
44
|
-
var mcp_config_default = "{\n \"_comment\": \"MCP Server Configuration - Use ${VAR_NAME} syntax for environment variable interpolation\",\n \"_instructions\": \"config.instruction: Server's default instruction | instruction: User override (takes precedence)\",\n \"mcpServers\": {\n \"example-server\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/mcp-server/build/index.js\"],\n \"env\": {\n \"LOG_LEVEL\": \"info\",\n \"_comment\": \"You can use environment variable interpolation:\",\n \"_example_DATABASE_URL\": \"${DATABASE_URL}\",\n \"_example_API_KEY\": \"${MY_API_KEY}\"\n },\n \"config\": {\n \"instruction\": \"Use this server for...\"\n },\n \"_instruction_override\": \"Optional user override - takes precedence over config.instruction\"\n }\n }\n}\n";
|
|
45
|
-
|
|
46
48
|
//#endregion
|
|
47
49
|
//#region src/commands/init.ts
|
|
48
50
|
/**
|
|
@@ -159,12 +161,6 @@ async function findExistingHealthyRuntime(workspaceRoot) {
|
|
|
159
161
|
} catch {}
|
|
160
162
|
return null;
|
|
161
163
|
}
|
|
162
|
-
function resolveSiblingRegistryPath(registryPath, fileName) {
|
|
163
|
-
if (!registryPath) return;
|
|
164
|
-
const resolved = node_path.default.resolve(registryPath);
|
|
165
|
-
if (node_path.default.extname(resolved) === ".json") return node_path.default.join(node_path.default.dirname(resolved), fileName);
|
|
166
|
-
return node_path.default.join(resolved, fileName);
|
|
167
|
-
}
|
|
168
164
|
function buildCliCandidates() {
|
|
169
165
|
const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
170
166
|
const __dirname$1 = node_path.default.dirname(__filename$1);
|
|
@@ -269,7 +265,7 @@ async function prestartHttpRuntime(options) {
|
|
|
269
265
|
...process.env,
|
|
270
266
|
...registryPath ? {
|
|
271
267
|
PORT_REGISTRY_PATH: registryPath,
|
|
272
|
-
PROCESS_REGISTRY_PATH: resolveSiblingRegistryPath(registryPath, "processes.json")
|
|
268
|
+
PROCESS_REGISTRY_PATH: (0, __agimon_ai_foundation_process_registry.resolveSiblingRegistryPath)(registryPath, "processes.json")
|
|
273
269
|
} : {}
|
|
274
270
|
};
|
|
275
271
|
const child = spawnBackgroundRuntime([
|
|
@@ -387,16 +383,37 @@ async function findConfigFileAsync() {
|
|
|
387
383
|
const configPath = (0, node_path.resolve)(projectPath, fileName);
|
|
388
384
|
if (await pathExists(configPath)) return configPath;
|
|
389
385
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
386
|
+
const MAX_PARENT_LEVELS = 3;
|
|
387
|
+
let searchDir = process.cwd();
|
|
388
|
+
for (let level = 0; level <= MAX_PARENT_LEVELS; level++) {
|
|
389
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
390
|
+
const configPath = (0, node_path.join)(searchDir, fileName);
|
|
391
|
+
if (await pathExists(configPath)) return configPath;
|
|
392
|
+
}
|
|
393
|
+
const parentDir = (0, node_path.dirname)(searchDir);
|
|
394
|
+
if (parentDir === searchDir) break;
|
|
395
|
+
searchDir = parentDir;
|
|
394
396
|
}
|
|
395
397
|
return null;
|
|
396
398
|
} catch (error) {
|
|
397
399
|
throw new Error(`Failed to discover MCP config file: ${toErrorMessage$9(error)}`);
|
|
398
400
|
}
|
|
399
401
|
}
|
|
402
|
+
function loadProxyDefaults(configPath) {
|
|
403
|
+
try {
|
|
404
|
+
const content = (0, node_fs.readFileSync)(configPath, "utf-8");
|
|
405
|
+
const proxy = (configPath.endsWith(".yaml") || configPath.endsWith(".yml") ? js_yaml.default.load(content) : JSON.parse(content))?.proxy;
|
|
406
|
+
if (!proxy || typeof proxy !== "object") return {};
|
|
407
|
+
const p = proxy;
|
|
408
|
+
return {
|
|
409
|
+
type: typeof p.type === "string" ? p.type : void 0,
|
|
410
|
+
port: typeof p.port === "number" && Number.isInteger(p.port) && p.port > 0 ? p.port : void 0,
|
|
411
|
+
host: typeof p.host === "string" ? p.host : void 0
|
|
412
|
+
};
|
|
413
|
+
} catch {
|
|
414
|
+
return {};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
400
417
|
async function resolveServerId(options, resolvedConfigPath) {
|
|
401
418
|
const container = require_src.createProxyIoCContainer();
|
|
402
419
|
if (options.id) return options.id;
|
|
@@ -418,12 +435,12 @@ function validateTransportType(type) {
|
|
|
418
435
|
function validateProxyMode(mode) {
|
|
419
436
|
if (!isValidProxyMode(mode)) throw new Error(`Unknown proxy mode: '${mode}'. Valid options: meta, flat, search`);
|
|
420
437
|
}
|
|
421
|
-
function createTransportConfig(options, mode) {
|
|
438
|
+
function createTransportConfig(options, mode, proxyDefaults) {
|
|
422
439
|
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
423
440
|
return {
|
|
424
441
|
mode,
|
|
425
|
-
port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0),
|
|
426
|
-
host: options.host
|
|
442
|
+
port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0) ?? proxyDefaults?.port,
|
|
443
|
+
host: options.host ?? process.env.MCP_HOST ?? proxyDefaults?.host ?? DEFAULT_HOST
|
|
427
444
|
};
|
|
428
445
|
}
|
|
429
446
|
function createStdioSafeLogger() {
|
|
@@ -472,9 +489,6 @@ function createRuntimeRecord(serverId, config, port, shutdownToken, configPath)
|
|
|
472
489
|
function createPortRegistryService() {
|
|
473
490
|
return new __agimon_ai_foundation_port_registry.PortRegistryService(process.env.PORT_REGISTRY_PATH);
|
|
474
491
|
}
|
|
475
|
-
function createProcessRegistryService() {
|
|
476
|
-
return new __agimon_ai_foundation_process_registry.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);
|
|
477
|
-
}
|
|
478
492
|
function getRegistryEnvironment() {
|
|
479
493
|
return process.env.NODE_ENV ?? "development";
|
|
480
494
|
}
|
|
@@ -521,41 +535,6 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
|
|
|
521
535
|
}
|
|
522
536
|
};
|
|
523
537
|
}
|
|
524
|
-
async function createProcessRegistryLease(serviceName, host, port, serverId, transport, configPath) {
|
|
525
|
-
const processRegistry = createProcessRegistryService();
|
|
526
|
-
const result = await processRegistry.registerProcess({
|
|
527
|
-
repositoryPath: getRegistryRepositoryPath(),
|
|
528
|
-
serviceName,
|
|
529
|
-
serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
|
|
530
|
-
environment: getRegistryEnvironment(),
|
|
531
|
-
pid: process.pid,
|
|
532
|
-
host,
|
|
533
|
-
port,
|
|
534
|
-
command: process.argv[1],
|
|
535
|
-
args: process.argv.slice(2),
|
|
536
|
-
metadata: {
|
|
537
|
-
transport,
|
|
538
|
-
serverId,
|
|
539
|
-
...configPath ? { configPath } : {}
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
if (!result.success || !result.record) throw new Error(result.error || `Failed to register process for ${serviceName}`);
|
|
543
|
-
let released = false;
|
|
544
|
-
return { release: async (options) => {
|
|
545
|
-
if (released) return;
|
|
546
|
-
released = true;
|
|
547
|
-
const releaseResult = await processRegistry.releaseProcess({
|
|
548
|
-
repositoryPath: getRegistryRepositoryPath(),
|
|
549
|
-
serviceName,
|
|
550
|
-
serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
|
|
551
|
-
pid: process.pid,
|
|
552
|
-
environment: getRegistryEnvironment(),
|
|
553
|
-
kill: options?.kill ?? false,
|
|
554
|
-
releasePort: options?.releasePort ?? false
|
|
555
|
-
});
|
|
556
|
-
if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes("No matching process entry")) throw new Error(releaseResult.error || `Failed to release process for ${serviceName}`);
|
|
557
|
-
} };
|
|
558
|
-
}
|
|
559
538
|
async function releasePortLease(lease) {
|
|
560
539
|
if (!lease) return;
|
|
561
540
|
await lease.release();
|
|
@@ -589,13 +568,19 @@ async function stopOwnedHttpTransport(handler, runtimeStateService, serverId, pr
|
|
|
589
568
|
throw new Error(`Failed to stop owned HTTP transport '${serverId}': ${toErrorMessage$9(error)}`);
|
|
590
569
|
}
|
|
591
570
|
} finally {
|
|
592
|
-
await processLease?.release({
|
|
593
|
-
kill: false,
|
|
594
|
-
releasePort: false
|
|
595
|
-
});
|
|
571
|
+
await processLease?.release({ kill: false });
|
|
596
572
|
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
597
573
|
}
|
|
598
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Run post-stop cleanup for an HTTP runtime (release port, dispose services, remove state).
|
|
577
|
+
* This is the subset of stopOwnedHttpTransport that runs AFTER handler.stop() has already
|
|
578
|
+
* been called by startServer()'s signal handler — avoids double-stopping the transport.
|
|
579
|
+
*/
|
|
580
|
+
async function cleanupHttpRuntime(runtimeStateService, serverId, processLease) {
|
|
581
|
+
await processLease?.release({ kill: false });
|
|
582
|
+
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
583
|
+
}
|
|
599
584
|
async function cleanupFailedRuntimeStartup(handler, runtimeStateService, serverId, processLease) {
|
|
600
585
|
try {
|
|
601
586
|
try {
|
|
@@ -604,10 +589,7 @@ async function cleanupFailedRuntimeStartup(handler, runtimeStateService, serverI
|
|
|
604
589
|
throw new Error(`Failed to stop HTTP transport during cleanup for '${serverId}': ${toErrorMessage$9(error)}`);
|
|
605
590
|
}
|
|
606
591
|
} finally {
|
|
607
|
-
await processLease?.release({
|
|
608
|
-
kill: false,
|
|
609
|
-
releasePort: false
|
|
610
|
-
});
|
|
592
|
+
await processLease?.release({ kill: false });
|
|
611
593
|
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
612
594
|
}
|
|
613
595
|
}
|
|
@@ -652,7 +634,21 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
652
634
|
...config,
|
|
653
635
|
port: runtimePort
|
|
654
636
|
};
|
|
655
|
-
const processLease = await
|
|
637
|
+
const processLease = await (0, __agimon_ai_foundation_process_registry.createProcessLease)({
|
|
638
|
+
repositoryPath: getRegistryRepositoryPath(),
|
|
639
|
+
serviceName: PROCESS_REGISTRY_SERVICE_HTTP,
|
|
640
|
+
serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
|
|
641
|
+
environment: getRegistryEnvironment(),
|
|
642
|
+
host: runtimeConfig.host ?? DEFAULT_HOST,
|
|
643
|
+
port: runtimePort,
|
|
644
|
+
command: process.argv[1],
|
|
645
|
+
args: process.argv.slice(2),
|
|
646
|
+
metadata: {
|
|
647
|
+
transport: TRANSPORT_TYPE_HTTP,
|
|
648
|
+
serverId: runtimeServerId,
|
|
649
|
+
...resolvedConfigPath ? { configPath: resolvedConfigPath } : {}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
656
652
|
let releasePort = async () => {
|
|
657
653
|
await releasePortLease(portLease ?? null);
|
|
658
654
|
releasePort = async () => void 0;
|
|
@@ -676,10 +672,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
676
672
|
handler = new require_src.HttpTransportHandler(() => require_src.createSessionServer(sharedServices), runtimeConfig, createHttpAdminOptions(runtimeRecord.serverId, shutdownToken, stopHandler));
|
|
677
673
|
} catch (error) {
|
|
678
674
|
await releasePort();
|
|
679
|
-
await processLease.release({
|
|
680
|
-
kill: false,
|
|
681
|
-
releasePort: false
|
|
682
|
-
});
|
|
675
|
+
await processLease.release({ kill: false });
|
|
683
676
|
await sharedServices.dispose();
|
|
684
677
|
throw new Error(`Failed to create HTTP runtime server: ${toErrorMessage$9(error)}`);
|
|
685
678
|
}
|
|
@@ -687,19 +680,11 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
|
|
|
687
680
|
await startServer(handler, async () => {
|
|
688
681
|
await releasePort();
|
|
689
682
|
await sharedServices.dispose();
|
|
690
|
-
await processLease
|
|
691
|
-
kill: false,
|
|
692
|
-
releasePort: false
|
|
693
|
-
});
|
|
694
|
-
await removeRuntimeRecord(runtimeStateService, runtimeRecord.serverId);
|
|
683
|
+
await cleanupHttpRuntime(runtimeStateService, runtimeRecord.serverId, processLease);
|
|
695
684
|
});
|
|
696
685
|
await writeRuntimeRecord(runtimeStateService, runtimeRecord);
|
|
697
686
|
} catch (error) {
|
|
698
687
|
await releasePort();
|
|
699
|
-
await processLease.release({
|
|
700
|
-
kill: false,
|
|
701
|
-
releasePort: false
|
|
702
|
-
});
|
|
703
688
|
await sharedServices.dispose();
|
|
704
689
|
await cleanupFailedRuntimeStartup(handler, runtimeStateService, runtimeRecord.serverId, processLease);
|
|
705
690
|
throw new Error(`Failed to start HTTP runtime '${runtimeRecord.serverId}': ${toErrorMessage$9(error)}`);
|
|
@@ -779,21 +764,21 @@ async function startStdioHttpTransport(config, options, resolvedConfigPath) {
|
|
|
779
764
|
throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$9(error)}`);
|
|
780
765
|
}
|
|
781
766
|
}
|
|
782
|
-
async function startTransport(transportType, options, resolvedConfigPath, serverOptions) {
|
|
767
|
+
async function startTransport(transportType, options, resolvedConfigPath, serverOptions, proxyDefaults) {
|
|
783
768
|
try {
|
|
784
769
|
if (transportType === TRANSPORT_TYPE_STDIO) {
|
|
785
770
|
await startStdioTransport(serverOptions);
|
|
786
771
|
return;
|
|
787
772
|
}
|
|
788
773
|
if (transportType === TRANSPORT_TYPE_HTTP) {
|
|
789
|
-
await createAndStartHttpRuntime(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP), resolvedConfigPath);
|
|
774
|
+
await createAndStartHttpRuntime(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP, proxyDefaults), resolvedConfigPath);
|
|
790
775
|
return;
|
|
791
776
|
}
|
|
792
777
|
if (transportType === TRANSPORT_TYPE_SSE) {
|
|
793
|
-
await startSseTransport(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.SSE));
|
|
778
|
+
await startSseTransport(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.SSE, proxyDefaults));
|
|
794
779
|
return;
|
|
795
780
|
}
|
|
796
|
-
await startStdioHttpTransport(createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP), options, resolvedConfigPath);
|
|
781
|
+
await startStdioHttpTransport(createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP, proxyDefaults), options, resolvedConfigPath);
|
|
797
782
|
} catch (error) {
|
|
798
783
|
throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$9(error)}`);
|
|
799
784
|
}
|
|
@@ -801,18 +786,19 @@ async function startTransport(transportType, options, resolvedConfigPath, server
|
|
|
801
786
|
/**
|
|
802
787
|
* MCP Serve command
|
|
803
788
|
*/
|
|
804
|
-
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}
|
|
789
|
+
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}`).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").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) => {
|
|
805
790
|
try {
|
|
806
|
-
const transportType = validateTransportType(options.type.toLowerCase());
|
|
807
|
-
validateProxyMode(options.proxyMode);
|
|
808
791
|
const resolvedConfigPath = options.config || await findConfigFileAsync() || void 0;
|
|
809
|
-
|
|
792
|
+
const proxyDefaults = resolvedConfigPath ? loadProxyDefaults(resolvedConfigPath) : {};
|
|
793
|
+
const transportType = validateTransportType((options.type ?? proxyDefaults.type ?? TRANSPORT_TYPE_STDIO).toLowerCase());
|
|
794
|
+
validateProxyMode(options.proxyMode);
|
|
795
|
+
await startTransport(transportType, options, resolvedConfigPath, createServerOptions(options, resolvedConfigPath, await resolveServerId(options, resolvedConfigPath)), proxyDefaults);
|
|
810
796
|
} catch (error) {
|
|
811
|
-
const rawTransportType = options.type.toLowerCase();
|
|
797
|
+
const rawTransportType = (options.type ?? TRANSPORT_TYPE_STDIO).toLowerCase();
|
|
812
798
|
const transportType = isValidTransportType(rawTransportType) ? rawTransportType : TRANSPORT_TYPE_STDIO;
|
|
813
799
|
const envPort = process.env.MCP_PORT ? Number(process.env.MCP_PORT) : void 0;
|
|
814
800
|
const requestedPort = options.port ?? (Number.isFinite(envPort) ? envPort : void 0);
|
|
815
|
-
console.error(formatStartError(transportType, options.host, requestedPort, error));
|
|
801
|
+
console.error(formatStartError(transportType, options.host ?? DEFAULT_HOST, requestedPort, error));
|
|
816
802
|
process.exit(1);
|
|
817
803
|
}
|
|
818
804
|
});
|
|
@@ -822,14 +808,58 @@ const mcpServeCommand = new commander.Command("mcp-serve").description("Start MC
|
|
|
822
808
|
function toErrorMessage$8(error) {
|
|
823
809
|
return error instanceof Error ? error.message : String(error);
|
|
824
810
|
}
|
|
825
|
-
async function
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
811
|
+
async function checkHealth(host, port) {
|
|
812
|
+
try {
|
|
813
|
+
return (await fetch(`http://${host}:${port}/health`, { signal: AbortSignal.timeout(3e3) })).ok;
|
|
814
|
+
} catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Proxy mode: connect to a running HTTP server instead of downstream servers directly.
|
|
820
|
+
* Auto-starts the server if not running.
|
|
821
|
+
*/
|
|
822
|
+
async function withProxiedContext(container, config, configFilePath, options, run) {
|
|
823
|
+
const host = config.proxy?.host ?? "localhost";
|
|
824
|
+
const port = config.proxy?.port;
|
|
825
|
+
const endpoint = `http://${host}:${port}/mcp`;
|
|
826
|
+
if (!await checkHealth(host, port)) {
|
|
827
|
+
if (!options.json) console.error("Starting HTTP proxy server in background...");
|
|
828
|
+
await prestartHttpRuntime({
|
|
829
|
+
host,
|
|
830
|
+
port,
|
|
831
|
+
config: configFilePath,
|
|
832
|
+
cache: options.useCache !== false,
|
|
833
|
+
clearDefinitionsCache: false,
|
|
834
|
+
proxyMode: "flat"
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
const clientManager = container.createClientManagerService();
|
|
838
|
+
try {
|
|
839
|
+
await clientManager.connectToServer("proxy", {
|
|
840
|
+
name: "proxy",
|
|
841
|
+
transport: "http",
|
|
842
|
+
config: { url: endpoint }
|
|
843
|
+
});
|
|
844
|
+
if (!options.json) console.error(`✓ Connected to proxy at ${endpoint}`);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
throw new Error(`Failed to connect to proxy server at ${endpoint}: ${toErrorMessage$8(error)}`);
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
return await run({
|
|
850
|
+
container,
|
|
851
|
+
configFilePath,
|
|
852
|
+
config,
|
|
853
|
+
clientManager
|
|
854
|
+
});
|
|
855
|
+
} finally {
|
|
856
|
+
await clientManager.disconnectAll();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Direct mode: connect to all downstream MCP servers individually.
|
|
861
|
+
*/
|
|
862
|
+
async function withDirectContext(container, config, configFilePath, options, run) {
|
|
833
863
|
const clientManager = container.createClientManagerService();
|
|
834
864
|
await Promise.all(Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
|
|
835
865
|
try {
|
|
@@ -851,6 +881,17 @@ async function withConnectedCommandContext(options, run) {
|
|
|
851
881
|
await clientManager.disconnectAll();
|
|
852
882
|
}
|
|
853
883
|
}
|
|
884
|
+
async function withConnectedCommandContext(options, run) {
|
|
885
|
+
const container = require_src.createProxyIoCContainer();
|
|
886
|
+
const configFilePath = options.config || require_src.findConfigFile();
|
|
887
|
+
if (!configFilePath) throw new Error("No config file found. Use --config or create mcp-config.yaml");
|
|
888
|
+
const config = await container.createConfigFetcherService({
|
|
889
|
+
configFilePath,
|
|
890
|
+
useCache: options.useCache
|
|
891
|
+
}).fetchConfiguration();
|
|
892
|
+
if (config.proxy?.port) return await withProxiedContext(container, config, configFilePath, options, run);
|
|
893
|
+
return await withDirectContext(container, config, configFilePath, options, run);
|
|
894
|
+
}
|
|
854
895
|
|
|
855
896
|
//#endregion
|
|
856
897
|
//#region src/commands/list-tools.ts
|
|
@@ -1093,7 +1134,7 @@ function toErrorMessage$5(error) {
|
|
|
1093
1134
|
/**
|
|
1094
1135
|
* Execute an MCP tool with arguments
|
|
1095
1136
|
*/
|
|
1096
|
-
const useToolCommand = new commander.Command("use-tool").description("Execute an MCP tool with arguments").argument("<toolName>", "Tool name to execute").option("-c, --config <path>", "Path to MCP server configuration file").option("-s, --server <name>", "Server name (required if tool exists on multiple servers)").option("-a, --args <json>", "Tool arguments as JSON string", "{}").option("-t, --timeout <ms>", "Request timeout in milliseconds for tool execution (default: 60000)", parseInt).option("-j, --json", "Output as JSON", false).action(async (toolName, options) => {
|
|
1137
|
+
const useToolCommand = new commander.Command("use-tool").description("Execute an MCP tool with arguments").argument("<toolName>", "Tool name to execute").option("-c, --config <path>", "Path to MCP server configuration file").option("-s, --server <name>", "Server name (required if tool exists on multiple servers)").option("-a, --args <json>", "Tool arguments as JSON string", "{}").option("-t, --timeout <ms>", "Request timeout in milliseconds for tool execution (default: 60000)", Number.parseInt).option("-j, --json", "Output as JSON", false).action(async (toolName, options) => {
|
|
1097
1138
|
try {
|
|
1098
1139
|
let toolArgs = {};
|
|
1099
1140
|
try {
|