@agiflowai/one-mcp 0.3.12 → 0.3.14
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/README.md +9 -3
- package/dist/cli.cjs +579 -241
- package/dist/cli.mjs +541 -203
- package/dist/index.cjs +18 -12
- package/dist/index.d.cts +332 -23
- package/dist/index.d.mts +333 -23
- package/dist/index.mjs +2 -2
- package/dist/{http-ClRbCldm.mjs → src-D7Yq1bTx.mjs} +700 -167
- package/dist/{http-B4_JfY6t.cjs → src-iTE9Cero.cjs} +723 -166
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,192 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
2
|
+
import { _ as findConfigFile, a as SseTransportHandler, c as createServer, d as SearchListToolsTool, g as generateServerId, h as DefinitionsCacheService, i as StdioTransportHandler, l as version, m as McpClientManagerService, n as RuntimeStateService, o as HttpTransportHandler, p as SkillService, r as StdioHttpTransportHandler, s as TRANSPORT_MODE, t as StopServerService, v as ConfigFetcherService } from "./src-D7Yq1bTx.mjs";
|
|
3
|
+
import { access, writeFile } from "node:fs/promises";
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
5
7
|
import { Liquid } from "liquidjs";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
6
9
|
import { Command } from "commander";
|
|
7
10
|
import { log, print } from "@agiflowai/aicode-utils";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
|
|
10
|
-
//#region src/templates/mcp-config.yaml.liquid?raw
|
|
11
|
-
var mcp_config_yaml_default = "# MCP Server Configuration\n# This file configures the MCP servers that one-mcp 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";
|
|
12
|
-
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/templates/mcp-config.json?raw
|
|
15
|
-
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";
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/commands/init.ts
|
|
19
|
-
/**
|
|
20
|
-
* Init Command
|
|
21
|
-
*
|
|
22
|
-
* DESIGN PATTERNS:
|
|
23
|
-
* - Command pattern with Commander for CLI argument parsing
|
|
24
|
-
* - Async/await pattern for asynchronous operations
|
|
25
|
-
* - Error handling pattern with try-catch and proper exit codes
|
|
26
|
-
*
|
|
27
|
-
* CODING STANDARDS:
|
|
28
|
-
* - Use async action handlers for asynchronous operations
|
|
29
|
-
* - Provide clear option descriptions and default values
|
|
30
|
-
* - Handle errors gracefully with process.exit()
|
|
31
|
-
* - Log progress and errors to console
|
|
32
|
-
* - Use Commander's .option() and .argument() for inputs
|
|
33
|
-
*
|
|
34
|
-
* AVOID:
|
|
35
|
-
* - Synchronous blocking operations in action handlers
|
|
36
|
-
* - Missing error handling (always use try-catch)
|
|
37
|
-
* - Hardcoded values (use options or environment variables)
|
|
38
|
-
* - Not exiting with appropriate exit codes on errors
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* Initialize MCP configuration file
|
|
42
|
-
*/
|
|
43
|
-
const initCommand = new Command("init").description("Initialize MCP configuration file").option("-o, --output <path>", "Output file path", "mcp-config.yaml").option("--json", "Generate JSON config instead of YAML", false).option("-f, --force", "Overwrite existing config file", false).option("--mcp-servers <json>", "JSON string of MCP servers to add to config (optional)").action(async (options) => {
|
|
44
|
-
try {
|
|
45
|
-
const outputPath = resolve(options.output);
|
|
46
|
-
const isYaml = !options.json && (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml"));
|
|
47
|
-
let content;
|
|
48
|
-
if (isYaml) {
|
|
49
|
-
const liquid = new Liquid();
|
|
50
|
-
let mcpServersData = null;
|
|
51
|
-
if (options.mcpServers) try {
|
|
52
|
-
const serversObj = JSON.parse(options.mcpServers);
|
|
53
|
-
mcpServersData = Object.entries(serversObj).map(([name, config]) => ({
|
|
54
|
-
name,
|
|
55
|
-
command: config.command,
|
|
56
|
-
args: config.args
|
|
57
|
-
}));
|
|
58
|
-
} catch (parseError) {
|
|
59
|
-
log.error("Failed to parse --mcp-servers JSON:", parseError instanceof Error ? parseError.message : String(parseError));
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
content = await liquid.parseAndRender(mcp_config_yaml_default, { mcpServers: mcpServersData });
|
|
63
|
-
} else content = mcp_config_default;
|
|
64
|
-
try {
|
|
65
|
-
await writeFile(outputPath, content, {
|
|
66
|
-
encoding: "utf-8",
|
|
67
|
-
flag: options.force ? "w" : "wx"
|
|
68
|
-
});
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
71
|
-
log.error(`Config file already exists: ${outputPath}`);
|
|
72
|
-
log.info("Use --force to overwrite");
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
log.info(`MCP configuration file created: ${outputPath}`);
|
|
78
|
-
log.info("Next steps:");
|
|
79
|
-
log.info("1. Edit the configuration file to add your MCP servers");
|
|
80
|
-
log.info(`2. Run: one-mcp mcp-serve --config ${outputPath}`);
|
|
81
|
-
} catch (error) {
|
|
82
|
-
log.error("Error executing init:", error instanceof Error ? error.message : String(error));
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
//#endregion
|
|
88
|
-
//#region src/types/index.ts
|
|
89
|
-
/**
|
|
90
|
-
* Transport mode constants
|
|
91
|
-
*/
|
|
92
|
-
const TRANSPORT_MODE = {
|
|
93
|
-
STDIO: "stdio",
|
|
94
|
-
HTTP: "http",
|
|
95
|
-
SSE: "sse"
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
//#endregion
|
|
99
|
-
//#region src/commands/mcp-serve.ts
|
|
100
|
-
/**
|
|
101
|
-
* MCP Serve Command
|
|
102
|
-
*
|
|
103
|
-
* DESIGN PATTERNS:
|
|
104
|
-
* - Command pattern with Commander for CLI argument parsing
|
|
105
|
-
* - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)
|
|
106
|
-
* - Factory pattern for creating transport handlers
|
|
107
|
-
* - Graceful shutdown pattern with signal handling
|
|
108
|
-
*
|
|
109
|
-
* CODING STANDARDS:
|
|
110
|
-
* - Use async/await for asynchronous operations
|
|
111
|
-
* - Implement proper error handling with try-catch blocks
|
|
112
|
-
* - Handle process signals for graceful shutdown
|
|
113
|
-
* - Provide clear CLI options and help messages
|
|
114
|
-
*
|
|
115
|
-
* AVOID:
|
|
116
|
-
* - Hardcoded configuration values (use CLI options or environment variables)
|
|
117
|
-
* - Missing error handling for transport startup
|
|
118
|
-
* - Not cleaning up resources on shutdown
|
|
119
|
-
*/
|
|
120
|
-
/**
|
|
121
|
-
* Type guard to validate transport type
|
|
122
|
-
* @param type - The transport type string to validate
|
|
123
|
-
* @returns True if the type is a valid transport type
|
|
124
|
-
*/
|
|
125
|
-
function isValidTransportType(type) {
|
|
126
|
-
return type === "stdio" || type === "http" || type === "sse";
|
|
127
|
-
}
|
|
128
|
-
function isValidProxyMode(mode) {
|
|
129
|
-
return mode === "meta" || mode === "flat" || mode === "search";
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Start MCP server with given transport handler
|
|
133
|
-
* @param handler - The transport handler to start
|
|
134
|
-
*/
|
|
135
|
-
async function startServer(handler) {
|
|
136
|
-
await handler.start();
|
|
137
|
-
const shutdown = async (signal) => {
|
|
138
|
-
console.error(`\nReceived ${signal}, shutting down gracefully...`);
|
|
139
|
-
try {
|
|
140
|
-
await handler.stop();
|
|
141
|
-
process.exit(0);
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error("Error during shutdown:", error);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
148
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* MCP Serve command
|
|
152
|
-
*/
|
|
153
|
-
const mcpServeCommand = new Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", "Transport type: stdio, http, or sse", "stdio").option("-p, --port <port>", "Port to listen on (http/sse only)", (val) => parseInt(val, 10), 3e3).option("--host <host>", "Host to bind to (http/sse only)", "localhost").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 one-mcp 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) => {
|
|
154
|
-
const transportType = options.type.toLowerCase();
|
|
155
|
-
if (!isValidTransportType(transportType)) {
|
|
156
|
-
console.error(`Unknown transport type: '${transportType}'. Valid options: stdio, http, sse`);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
if (!isValidProxyMode(options.proxyMode)) {
|
|
160
|
-
console.error(`Unknown proxy mode: '${options.proxyMode}'. Valid options: meta, flat, search`);
|
|
161
|
-
process.exit(1);
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
const serverOptions = {
|
|
165
|
-
configFilePath: options.config || findConfigFile() || void 0,
|
|
166
|
-
noCache: options.cache === false,
|
|
167
|
-
serverId: options.id,
|
|
168
|
-
definitionsCachePath: options.definitionsCache,
|
|
169
|
-
clearDefinitionsCache: options.clearDefinitionsCache,
|
|
170
|
-
proxyMode: options.proxyMode
|
|
171
|
-
};
|
|
172
|
-
if (transportType === "stdio") await startServer(new StdioTransportHandler(await createServer(serverOptions)));
|
|
173
|
-
else if (transportType === "http") await startServer(new HttpTransportHandler(await createServer(serverOptions), {
|
|
174
|
-
mode: TRANSPORT_MODE.HTTP,
|
|
175
|
-
port: options.port || Number(process.env.MCP_PORT) || 3e3,
|
|
176
|
-
host: options.host || process.env.MCP_HOST || "localhost"
|
|
177
|
-
}));
|
|
178
|
-
else if (transportType === "sse") await startServer(new SseTransportHandler(await createServer(serverOptions), {
|
|
179
|
-
mode: TRANSPORT_MODE.SSE,
|
|
180
|
-
port: options.port || Number(process.env.MCP_PORT) || 3e3,
|
|
181
|
-
host: options.host || process.env.MCP_HOST || "localhost"
|
|
182
|
-
}));
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error(`Failed to start MCP server with transport '${transportType}' on ${options.host}:${options.port}:`, error);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
11
|
|
|
189
|
-
//#endregion
|
|
190
12
|
//#region src/services/PrefetchService/constants.ts
|
|
191
13
|
/**
|
|
192
14
|
* PrefetchService Constants
|
|
@@ -564,6 +386,475 @@ var PrefetchService = class {
|
|
|
564
386
|
}
|
|
565
387
|
};
|
|
566
388
|
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/templates/mcp-config.yaml.liquid?raw
|
|
391
|
+
var mcp_config_yaml_default = "# MCP Server Configuration\n# This file configures the MCP servers that one-mcp 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";
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/templates/mcp-config.json?raw
|
|
395
|
+
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";
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/commands/init.ts
|
|
399
|
+
/**
|
|
400
|
+
* Init Command
|
|
401
|
+
*
|
|
402
|
+
* DESIGN PATTERNS:
|
|
403
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
404
|
+
* - Async/await pattern for asynchronous operations
|
|
405
|
+
* - Error handling pattern with try-catch and proper exit codes
|
|
406
|
+
*
|
|
407
|
+
* CODING STANDARDS:
|
|
408
|
+
* - Use async action handlers for asynchronous operations
|
|
409
|
+
* - Provide clear option descriptions and default values
|
|
410
|
+
* - Handle errors gracefully with process.exit()
|
|
411
|
+
* - Log progress and errors to console
|
|
412
|
+
* - Use Commander's .option() and .argument() for inputs
|
|
413
|
+
*
|
|
414
|
+
* AVOID:
|
|
415
|
+
* - Synchronous blocking operations in action handlers
|
|
416
|
+
* - Missing error handling (always use try-catch)
|
|
417
|
+
* - Hardcoded values (use options or environment variables)
|
|
418
|
+
* - Not exiting with appropriate exit codes on errors
|
|
419
|
+
*/
|
|
420
|
+
/**
|
|
421
|
+
* Initialize MCP configuration file
|
|
422
|
+
*/
|
|
423
|
+
const initCommand = new Command("init").description("Initialize MCP configuration file").option("-o, --output <path>", "Output file path", "mcp-config.yaml").option("--json", "Generate JSON config instead of YAML", false).option("-f, --force", "Overwrite existing config file", false).option("--mcp-servers <json>", "JSON string of MCP servers to add to config (optional)").action(async (options) => {
|
|
424
|
+
try {
|
|
425
|
+
const outputPath = resolve(options.output);
|
|
426
|
+
const isYaml = !options.json && (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml"));
|
|
427
|
+
let content;
|
|
428
|
+
if (isYaml) {
|
|
429
|
+
const liquid = new Liquid();
|
|
430
|
+
let mcpServersData = null;
|
|
431
|
+
if (options.mcpServers) try {
|
|
432
|
+
const serversObj = JSON.parse(options.mcpServers);
|
|
433
|
+
mcpServersData = Object.entries(serversObj).map(([name, config]) => ({
|
|
434
|
+
name,
|
|
435
|
+
command: config.command,
|
|
436
|
+
args: config.args
|
|
437
|
+
}));
|
|
438
|
+
} catch (parseError) {
|
|
439
|
+
log.error("Failed to parse --mcp-servers JSON:", parseError instanceof Error ? parseError.message : String(parseError));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
content = await liquid.parseAndRender(mcp_config_yaml_default, { mcpServers: mcpServersData });
|
|
443
|
+
} else content = mcp_config_default;
|
|
444
|
+
try {
|
|
445
|
+
await writeFile(outputPath, content, {
|
|
446
|
+
encoding: "utf-8",
|
|
447
|
+
flag: options.force ? "w" : "wx"
|
|
448
|
+
});
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
451
|
+
log.error(`Config file already exists: ${outputPath}`);
|
|
452
|
+
log.info("Use --force to overwrite");
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
log.info(`MCP configuration file created: ${outputPath}`);
|
|
458
|
+
log.info("Next steps:");
|
|
459
|
+
log.info("1. Edit the configuration file to add your MCP servers");
|
|
460
|
+
log.info(`2. Run: one-mcp mcp-serve --config ${outputPath}`);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
log.error("Error executing init:", error instanceof Error ? error.message : String(error));
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/commands/mcp-serve.ts
|
|
469
|
+
/**
|
|
470
|
+
* MCP Serve Command
|
|
471
|
+
*
|
|
472
|
+
* DESIGN PATTERNS:
|
|
473
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
474
|
+
* - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)
|
|
475
|
+
* - Factory pattern for creating transport handlers
|
|
476
|
+
* - Graceful shutdown pattern with signal handling
|
|
477
|
+
*
|
|
478
|
+
* CODING STANDARDS:
|
|
479
|
+
* - Use async/await for asynchronous operations
|
|
480
|
+
* - Implement proper error handling with try-catch blocks
|
|
481
|
+
* - Handle process signals for graceful shutdown
|
|
482
|
+
* - Provide clear CLI options and help messages
|
|
483
|
+
*
|
|
484
|
+
* AVOID:
|
|
485
|
+
* - Hardcoded configuration values (use CLI options or environment variables)
|
|
486
|
+
* - Missing error handling for transport startup
|
|
487
|
+
* - Not cleaning up resources on shutdown
|
|
488
|
+
*/
|
|
489
|
+
const CONFIG_FILE_NAMES = [
|
|
490
|
+
"mcp-config.yaml",
|
|
491
|
+
"mcp-config.yml",
|
|
492
|
+
"mcp-config.json"
|
|
493
|
+
];
|
|
494
|
+
const MCP_ENDPOINT_PATH = "/mcp";
|
|
495
|
+
const DEFAULT_PORT = 3e3;
|
|
496
|
+
const DEFAULT_HOST = "localhost";
|
|
497
|
+
const TRANSPORT_TYPE_STDIO = "stdio";
|
|
498
|
+
const TRANSPORT_TYPE_HTTP = "http";
|
|
499
|
+
const TRANSPORT_TYPE_SSE = "sse";
|
|
500
|
+
const TRANSPORT_TYPE_STDIO_HTTP = "stdio-http";
|
|
501
|
+
const RUNTIME_TRANSPORT = TRANSPORT_TYPE_HTTP;
|
|
502
|
+
const STDIO_HTTP_PROXY_STOP_LABEL = "Failed stopping stdio-http proxy";
|
|
503
|
+
const INTERNAL_HTTP_STOP_LABEL = "Failed stopping internal HTTP transport";
|
|
504
|
+
function toErrorMessage$6(error) {
|
|
505
|
+
return error instanceof Error ? error.message : String(error);
|
|
506
|
+
}
|
|
507
|
+
function isValidTransportType(type) {
|
|
508
|
+
return type === TRANSPORT_TYPE_STDIO || type === TRANSPORT_TYPE_HTTP || type === TRANSPORT_TYPE_SSE || type === TRANSPORT_TYPE_STDIO_HTTP;
|
|
509
|
+
}
|
|
510
|
+
function isValidProxyMode(mode) {
|
|
511
|
+
return mode === "meta" || mode === "flat" || mode === "search";
|
|
512
|
+
}
|
|
513
|
+
function isAddressInUseError(error) {
|
|
514
|
+
if (error instanceof Error && error.message.includes("EADDRINUSE")) return true;
|
|
515
|
+
if (typeof error !== "object" || error === null) return false;
|
|
516
|
+
if ("code" in error && error.code === "EADDRINUSE") return true;
|
|
517
|
+
if ("message" in error && typeof error.message === "string" && error.message.includes("EADDRINUSE")) return true;
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
async function pathExists(filePath) {
|
|
521
|
+
try {
|
|
522
|
+
await access(filePath, constants.F_OK);
|
|
523
|
+
return true;
|
|
524
|
+
} catch {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function findConfigFileAsync() {
|
|
529
|
+
try {
|
|
530
|
+
const projectPath = process.env.PROJECT_PATH;
|
|
531
|
+
if (projectPath) for (const fileName of CONFIG_FILE_NAMES) {
|
|
532
|
+
const configPath = resolve(projectPath, fileName);
|
|
533
|
+
if (await pathExists(configPath)) return configPath;
|
|
534
|
+
}
|
|
535
|
+
const cwd = process.cwd();
|
|
536
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
537
|
+
const configPath = join(cwd, fileName);
|
|
538
|
+
if (await pathExists(configPath)) return configPath;
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new Error(`Failed to discover MCP config file: ${toErrorMessage$6(error)}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function resolveServerId(options, resolvedConfigPath) {
|
|
546
|
+
if (options.id) return options.id;
|
|
547
|
+
if (resolvedConfigPath) try {
|
|
548
|
+
const config = await new ConfigFetcherService({
|
|
549
|
+
configFilePath: resolvedConfigPath,
|
|
550
|
+
useCache: options.cache !== false
|
|
551
|
+
}).fetchConfiguration(options.cache === false);
|
|
552
|
+
if (config.id) return config.id;
|
|
553
|
+
} catch (error) {
|
|
554
|
+
throw new Error(`Failed to resolve server ID from config '${resolvedConfigPath}': ${toErrorMessage$6(error)}`);
|
|
555
|
+
}
|
|
556
|
+
return generateServerId();
|
|
557
|
+
}
|
|
558
|
+
function validateTransportType(type) {
|
|
559
|
+
if (!isValidTransportType(type)) throw new Error(`Unknown transport type: '${type}'. Valid options: ${TRANSPORT_TYPE_STDIO}, ${TRANSPORT_TYPE_HTTP}, ${TRANSPORT_TYPE_SSE}, ${TRANSPORT_TYPE_STDIO_HTTP}`);
|
|
560
|
+
return type;
|
|
561
|
+
}
|
|
562
|
+
function validateProxyMode(mode) {
|
|
563
|
+
if (!isValidProxyMode(mode)) throw new Error(`Unknown proxy mode: '${mode}'. Valid options: meta, flat, search`);
|
|
564
|
+
}
|
|
565
|
+
function createTransportConfig(options, mode) {
|
|
566
|
+
return {
|
|
567
|
+
mode,
|
|
568
|
+
port: options.port || Number(process.env.MCP_PORT) || DEFAULT_PORT,
|
|
569
|
+
host: options.host || process.env.MCP_HOST || DEFAULT_HOST
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function createServerOptions(options, resolvedConfigPath, serverId) {
|
|
573
|
+
return {
|
|
574
|
+
configFilePath: resolvedConfigPath,
|
|
575
|
+
noCache: options.cache === false,
|
|
576
|
+
serverId,
|
|
577
|
+
definitionsCachePath: options.definitionsCache,
|
|
578
|
+
clearDefinitionsCache: options.clearDefinitionsCache,
|
|
579
|
+
proxyMode: options.proxyMode
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function formatStartError(type, host, port, error) {
|
|
583
|
+
const startErrorMessage = toErrorMessage$6(error);
|
|
584
|
+
if (type === TRANSPORT_TYPE_STDIO) return `Failed to start MCP server with transport '${type}': ${startErrorMessage}`;
|
|
585
|
+
return `Failed to start MCP server with transport '${type}' on ${host}:${port}: ${startErrorMessage}`;
|
|
586
|
+
}
|
|
587
|
+
function createRuntimeRecord(serverId, config, shutdownToken, configPath) {
|
|
588
|
+
return {
|
|
589
|
+
serverId,
|
|
590
|
+
host: config.host ?? DEFAULT_HOST,
|
|
591
|
+
port: config.port ?? DEFAULT_PORT,
|
|
592
|
+
transport: RUNTIME_TRANSPORT,
|
|
593
|
+
shutdownToken,
|
|
594
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
595
|
+
pid: process.pid,
|
|
596
|
+
configPath
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function createHttpAdminOptions(serverId, shutdownToken, onShutdownRequested) {
|
|
600
|
+
return {
|
|
601
|
+
serverId,
|
|
602
|
+
shutdownToken,
|
|
603
|
+
onShutdownRequested
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
async function removeRuntimeRecord(runtimeStateService, serverId) {
|
|
607
|
+
try {
|
|
608
|
+
await runtimeStateService.remove(serverId);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
throw new Error(`Failed to remove runtime state for '${serverId}': ${toErrorMessage$6(error)}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function writeRuntimeRecord(runtimeStateService, record) {
|
|
614
|
+
try {
|
|
615
|
+
await runtimeStateService.write(record);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
throw new Error(`Failed to persist runtime state for '${record.serverId}': ${toErrorMessage$6(error)}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function stopOwnedHttpTransport(handler, runtimeStateService, serverId) {
|
|
621
|
+
try {
|
|
622
|
+
try {
|
|
623
|
+
await handler.stop();
|
|
624
|
+
} catch (error) {
|
|
625
|
+
throw new Error(`Failed to stop owned HTTP transport '${serverId}': ${toErrorMessage$6(error)}`);
|
|
626
|
+
}
|
|
627
|
+
} finally {
|
|
628
|
+
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async function cleanupFailedRuntimeStartup(handler, runtimeStateService, serverId) {
|
|
632
|
+
try {
|
|
633
|
+
try {
|
|
634
|
+
await handler.stop();
|
|
635
|
+
} catch (error) {
|
|
636
|
+
throw new Error(`Failed to stop HTTP transport during cleanup for '${serverId}': ${toErrorMessage$6(error)}`);
|
|
637
|
+
}
|
|
638
|
+
} finally {
|
|
639
|
+
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function stopTransportWithContext(label, stopOperation) {
|
|
643
|
+
try {
|
|
644
|
+
await stopOperation();
|
|
645
|
+
} catch (error) {
|
|
646
|
+
throw new Error(`${label}: ${toErrorMessage$6(error)}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
async function removeRuntimeRecordDuringStop(runtimeStateService, serverId) {
|
|
650
|
+
try {
|
|
651
|
+
await removeRuntimeRecord(runtimeStateService, serverId);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
throw new Error(`Failed to remove runtime state during HTTP stop callback for '${serverId}': ${toErrorMessage$6(error)}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async function createStdioHttpInternalTransport(serverOptions, config, adminOptions) {
|
|
657
|
+
try {
|
|
658
|
+
return new HttpTransportHandler(await createServer(serverOptions), config, adminOptions);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
throw new Error(`Failed to create internal HTTP transport for stdio-http proxy: ${toErrorMessage$6(error)}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Start MCP server with given transport handler
|
|
665
|
+
* @param handler - The transport handler to start
|
|
666
|
+
* @param onStopped - Optional cleanup callback run after signal-based shutdown
|
|
667
|
+
*/
|
|
668
|
+
async function startServer(handler, onStopped) {
|
|
669
|
+
try {
|
|
670
|
+
await handler.start();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
throw new Error(`Failed to start transport handler: ${toErrorMessage$6(error)}`);
|
|
673
|
+
}
|
|
674
|
+
const shutdown = async (signal) => {
|
|
675
|
+
console.error(`\nReceived ${signal}, shutting down gracefully...`);
|
|
676
|
+
try {
|
|
677
|
+
await handler.stop();
|
|
678
|
+
if (onStopped) await onStopped();
|
|
679
|
+
process.exit(0);
|
|
680
|
+
} catch (error) {
|
|
681
|
+
console.error(`Failed to gracefully stop transport during ${signal}: ${toErrorMessage$6(error)}`);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
process.on("SIGINT", async () => await shutdown("SIGINT"));
|
|
686
|
+
process.on("SIGTERM", async () => await shutdown("SIGTERM"));
|
|
687
|
+
}
|
|
688
|
+
async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPath) {
|
|
689
|
+
const runtimeStateService = new RuntimeStateService();
|
|
690
|
+
const shutdownToken = randomUUID();
|
|
691
|
+
const runtimeRecord = createRuntimeRecord(serverOptions.serverId ?? generateServerId(), config, shutdownToken, resolvedConfigPath);
|
|
692
|
+
let handler;
|
|
693
|
+
let isStopping = false;
|
|
694
|
+
const stopHandler = async () => {
|
|
695
|
+
if (isStopping) return;
|
|
696
|
+
isStopping = true;
|
|
697
|
+
try {
|
|
698
|
+
await stopOwnedHttpTransport(handler, runtimeStateService, runtimeRecord.serverId);
|
|
699
|
+
process.exit(0);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
throw new Error(`Failed to stop HTTP runtime '${runtimeRecord.serverId}' from admin shutdown: ${toErrorMessage$6(error)}`);
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
try {
|
|
705
|
+
handler = new HttpTransportHandler(await createServer(serverOptions), config, createHttpAdminOptions(runtimeRecord.serverId, shutdownToken, stopHandler));
|
|
706
|
+
} catch (error) {
|
|
707
|
+
throw new Error(`Failed to create HTTP runtime server: ${toErrorMessage$6(error)}`);
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
await startServer(handler, async () => {
|
|
711
|
+
await removeRuntimeRecordDuringStop(runtimeStateService, runtimeRecord.serverId);
|
|
712
|
+
});
|
|
713
|
+
await writeRuntimeRecord(runtimeStateService, runtimeRecord);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
await cleanupFailedRuntimeStartup(handler, runtimeStateService, runtimeRecord.serverId);
|
|
716
|
+
throw new Error(`Failed to start HTTP runtime '${runtimeRecord.serverId}': ${toErrorMessage$6(error)}`);
|
|
717
|
+
}
|
|
718
|
+
console.error(`Runtime state: http://${runtimeRecord.host}:${runtimeRecord.port} (${runtimeRecord.serverId})`);
|
|
719
|
+
}
|
|
720
|
+
async function stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport) {
|
|
721
|
+
try {
|
|
722
|
+
const stopOperations = [stopTransportWithContext(STDIO_HTTP_PROXY_STOP_LABEL, async () => {
|
|
723
|
+
await stdioHttpHandler.stop();
|
|
724
|
+
})];
|
|
725
|
+
if (ownsInternalHttpTransport && httpHandler) stopOperations.push(stopTransportWithContext(INTERNAL_HTTP_STOP_LABEL, async () => {
|
|
726
|
+
await httpHandler.stop();
|
|
727
|
+
}));
|
|
728
|
+
await Promise.all(stopOperations);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
throw new Error(`Failed to stop stdio-http transport: ${toErrorMessage$6(error)}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function startStdioTransport(serverOptions) {
|
|
734
|
+
try {
|
|
735
|
+
await startServer(new StdioTransportHandler(await createServer(serverOptions)));
|
|
736
|
+
} catch (error) {
|
|
737
|
+
throw new Error(`Failed to start stdio transport: ${toErrorMessage$6(error)}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async function startSseTransport(serverOptions, config) {
|
|
741
|
+
try {
|
|
742
|
+
await startServer(new SseTransportHandler(await createServer(serverOptions), config));
|
|
743
|
+
} catch (error) {
|
|
744
|
+
throw new Error(`Failed to start SSE transport: ${toErrorMessage$6(error)}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async function startStdioHttpTransport(serverOptions, config, resolvedConfigPath) {
|
|
748
|
+
try {
|
|
749
|
+
const stdioHttpHandler = new StdioHttpTransportHandler({ endpoint: new URL(`http://${config.host}:${config.port}${MCP_ENDPOINT_PATH}`) });
|
|
750
|
+
const runtimeStateService = new RuntimeStateService();
|
|
751
|
+
const serverId = serverOptions.serverId ?? generateServerId();
|
|
752
|
+
const shutdownToken = randomUUID();
|
|
753
|
+
let httpHandler = null;
|
|
754
|
+
let ownsInternalHttpTransport = false;
|
|
755
|
+
let isStopping = false;
|
|
756
|
+
const stopOwnedRuntime = async () => {
|
|
757
|
+
if (isStopping) return;
|
|
758
|
+
isStopping = true;
|
|
759
|
+
try {
|
|
760
|
+
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
761
|
+
ownsInternalHttpTransport = false;
|
|
762
|
+
process.exit(0);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error(`Unexpected error during admin shutdown: ${toErrorMessage$6(error)}`);
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
const adminOptions = createHttpAdminOptions(serverId, shutdownToken, stopOwnedRuntime);
|
|
769
|
+
await startServer({
|
|
770
|
+
async start() {
|
|
771
|
+
let initialProxyConnectError;
|
|
772
|
+
try {
|
|
773
|
+
await stdioHttpHandler.start();
|
|
774
|
+
return;
|
|
775
|
+
} catch (error) {
|
|
776
|
+
initialProxyConnectError = error;
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
httpHandler = await createStdioHttpInternalTransport(serverOptions, config, adminOptions);
|
|
780
|
+
await httpHandler.start();
|
|
781
|
+
ownsInternalHttpTransport = true;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (!isAddressInUseError(error)) throw new Error(`Failed to start internal HTTP transport for stdio-http proxy: ${toErrorMessage$6(error)}`);
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
await stdioHttpHandler.start();
|
|
787
|
+
} catch (error) {
|
|
788
|
+
let rollbackStopErrorMessage = "";
|
|
789
|
+
if (ownsInternalHttpTransport && httpHandler) {
|
|
790
|
+
try {
|
|
791
|
+
await httpHandler.stop();
|
|
792
|
+
} catch (stopError) {
|
|
793
|
+
rollbackStopErrorMessage = toErrorMessage$6(stopError);
|
|
794
|
+
}
|
|
795
|
+
ownsInternalHttpTransport = false;
|
|
796
|
+
}
|
|
797
|
+
const retryErrorMessage = toErrorMessage$6(error);
|
|
798
|
+
const initialErrorMessage = toErrorMessage$6(initialProxyConnectError);
|
|
799
|
+
const rollbackMessage = rollbackStopErrorMessage ? `; rollback stop failed: ${rollbackStopErrorMessage}` : "";
|
|
800
|
+
throw new Error(`Failed to start stdio-http proxy bridge: initial connect failed (${initialErrorMessage}); retry failed (${retryErrorMessage})${rollbackMessage}`);
|
|
801
|
+
}
|
|
802
|
+
if (ownsInternalHttpTransport) try {
|
|
803
|
+
await writeRuntimeRecord(runtimeStateService, createRuntimeRecord(serverId, config, shutdownToken, resolvedConfigPath));
|
|
804
|
+
} catch (error) {
|
|
805
|
+
throw new Error(`Failed to persist runtime state for stdio-http server '${serverId}': ${toErrorMessage$6(error)}`);
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
async stop() {
|
|
809
|
+
try {
|
|
810
|
+
await Promise.all([stopInternalHttpTransport(stdioHttpHandler, httpHandler, ownsInternalHttpTransport), removeRuntimeRecord(runtimeStateService, serverId)]);
|
|
811
|
+
ownsInternalHttpTransport = false;
|
|
812
|
+
} catch (error) {
|
|
813
|
+
ownsInternalHttpTransport = false;
|
|
814
|
+
throw new Error(`Failed during stdio-http shutdown for '${serverId}': ${toErrorMessage$6(error)}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
} catch (error) {
|
|
819
|
+
throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$6(error)}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async function startTransport(transportType, options, resolvedConfigPath, serverOptions) {
|
|
823
|
+
try {
|
|
824
|
+
if (transportType === TRANSPORT_TYPE_STDIO) {
|
|
825
|
+
await startStdioTransport(serverOptions);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (transportType === TRANSPORT_TYPE_HTTP) {
|
|
829
|
+
await createAndStartHttpRuntime(serverOptions, createTransportConfig(options, TRANSPORT_MODE.HTTP), resolvedConfigPath);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (transportType === TRANSPORT_TYPE_SSE) {
|
|
833
|
+
await startSseTransport(serverOptions, createTransportConfig(options, TRANSPORT_MODE.SSE));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
await startStdioHttpTransport(serverOptions, createTransportConfig(options, TRANSPORT_MODE.HTTP), resolvedConfigPath);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$6(error)}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* MCP Serve command
|
|
843
|
+
*/
|
|
844
|
+
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/stdio-http internal HTTP)", (val) => parseInt(val, 10), DEFAULT_PORT).option("--host <host>", "Host to bind to (http/sse/stdio-http internal HTTP)", DEFAULT_HOST).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How one-mcp 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) => {
|
|
845
|
+
try {
|
|
846
|
+
const transportType = validateTransportType(options.type.toLowerCase());
|
|
847
|
+
validateProxyMode(options.proxyMode);
|
|
848
|
+
const resolvedConfigPath = options.config || await findConfigFileAsync() || void 0;
|
|
849
|
+
await startTransport(transportType, options, resolvedConfigPath, createServerOptions(options, resolvedConfigPath, await resolveServerId(options, resolvedConfigPath)));
|
|
850
|
+
} catch (error) {
|
|
851
|
+
const rawTransportType = options.type.toLowerCase();
|
|
852
|
+
const transportType = isValidTransportType(rawTransportType) ? rawTransportType : TRANSPORT_TYPE_STDIO;
|
|
853
|
+
console.error(formatStartError(transportType, options.host, options.port, error));
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
|
|
567
858
|
//#endregion
|
|
568
859
|
//#region src/commands/list-tools.ts
|
|
569
860
|
/**
|
|
@@ -587,7 +878,7 @@ var PrefetchService = class {
|
|
|
587
878
|
* - Hardcoded values (use options or environment variables)
|
|
588
879
|
* - Not exiting with appropriate exit codes on errors
|
|
589
880
|
*/
|
|
590
|
-
function toErrorMessage$
|
|
881
|
+
function toErrorMessage$5(error) {
|
|
591
882
|
return error instanceof Error ? error.message : String(error);
|
|
592
883
|
}
|
|
593
884
|
function printSearchResults(result) {
|
|
@@ -618,7 +909,7 @@ const searchToolsCommand = new Command("search-tools").description("Search proxi
|
|
|
618
909
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
619
910
|
if (!options.json) console.error(`✓ Connected to ${serverName}`);
|
|
620
911
|
} catch (error) {
|
|
621
|
-
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$
|
|
912
|
+
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$5(error)}`);
|
|
622
913
|
}
|
|
623
914
|
});
|
|
624
915
|
await Promise.all(connectionPromises);
|
|
@@ -642,7 +933,7 @@ const searchToolsCommand = new Command("search-tools").description("Search proxi
|
|
|
642
933
|
}
|
|
643
934
|
await clientManager.disconnectAll();
|
|
644
935
|
} catch (error) {
|
|
645
|
-
console.error(`Error executing search-tools: ${toErrorMessage$
|
|
936
|
+
console.error(`Error executing search-tools: ${toErrorMessage$5(error)}`);
|
|
646
937
|
process.exit(1);
|
|
647
938
|
}
|
|
648
939
|
});
|
|
@@ -1001,7 +1292,7 @@ const useToolCommand = new Command("use-tool").description("Execute an MCP tool
|
|
|
1001
1292
|
* - Hardcoded values (use options or environment variables)
|
|
1002
1293
|
* - Not exiting with appropriate exit codes on errors
|
|
1003
1294
|
*/
|
|
1004
|
-
function toErrorMessage$
|
|
1295
|
+
function toErrorMessage$4(error) {
|
|
1005
1296
|
return error instanceof Error ? error.message : String(error);
|
|
1006
1297
|
}
|
|
1007
1298
|
/**
|
|
@@ -1018,7 +1309,7 @@ const listResourcesCommand = new Command("list-resources").description("List all
|
|
|
1018
1309
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
1019
1310
|
if (!options.json) console.error(`✓ Connected to ${serverName}`);
|
|
1020
1311
|
} catch (error) {
|
|
1021
|
-
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$
|
|
1312
|
+
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$4(error)}`);
|
|
1022
1313
|
}
|
|
1023
1314
|
}));
|
|
1024
1315
|
const clients = options.server ? [clientManager.getClient(options.server)].filter((c) => c !== void 0) : clientManager.getAllClients();
|
|
@@ -1041,7 +1332,7 @@ const listResourcesCommand = new Command("list-resources").description("List all
|
|
|
1041
1332
|
}
|
|
1042
1333
|
}));
|
|
1043
1334
|
for (const { serverName, resources, error } of resourceResults) {
|
|
1044
|
-
if (error && !options.json) console.error(`Failed to list resources from ${serverName}: ${toErrorMessage$
|
|
1335
|
+
if (error && !options.json) console.error(`Failed to list resources from ${serverName}: ${toErrorMessage$4(error)}`);
|
|
1045
1336
|
resourcesByServer[serverName] = resources;
|
|
1046
1337
|
}
|
|
1047
1338
|
if (options.json) console.log(JSON.stringify(resourcesByServer, null, 2));
|
|
@@ -1055,7 +1346,7 @@ const listResourcesCommand = new Command("list-resources").description("List all
|
|
|
1055
1346
|
}
|
|
1056
1347
|
await clientManager.disconnectAll();
|
|
1057
1348
|
} catch (error) {
|
|
1058
|
-
console.error(`Error executing list-resources: ${toErrorMessage$
|
|
1349
|
+
console.error(`Error executing list-resources: ${toErrorMessage$4(error)}`);
|
|
1059
1350
|
process.exit(1);
|
|
1060
1351
|
}
|
|
1061
1352
|
});
|
|
@@ -1083,7 +1374,7 @@ const listResourcesCommand = new Command("list-resources").description("List all
|
|
|
1083
1374
|
* - Hardcoded values (use options or environment variables)
|
|
1084
1375
|
* - Not exiting with appropriate exit codes on errors
|
|
1085
1376
|
*/
|
|
1086
|
-
function toErrorMessage$
|
|
1377
|
+
function toErrorMessage$3(error) {
|
|
1087
1378
|
return error instanceof Error ? error.message : String(error);
|
|
1088
1379
|
}
|
|
1089
1380
|
/**
|
|
@@ -1100,7 +1391,7 @@ const readResourceCommand = new Command("read-resource").description("Read a res
|
|
|
1100
1391
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
1101
1392
|
if (!options.json) console.error(`✓ Connected to ${serverName}`);
|
|
1102
1393
|
} catch (error) {
|
|
1103
|
-
console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$
|
|
1394
|
+
console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$3(error)}`);
|
|
1104
1395
|
}
|
|
1105
1396
|
}));
|
|
1106
1397
|
const clients = clientManager.getAllClients();
|
|
@@ -1135,7 +1426,7 @@ const readResourceCommand = new Command("read-resource").description("Read a res
|
|
|
1135
1426
|
const matchingServers = [];
|
|
1136
1427
|
for (const { serverName, hasResource, error } of searchResults) {
|
|
1137
1428
|
if (error) {
|
|
1138
|
-
console.error(`Failed to list resources from ${serverName}: ${toErrorMessage$
|
|
1429
|
+
console.error(`Failed to list resources from ${serverName}: ${toErrorMessage$3(error)}`);
|
|
1139
1430
|
continue;
|
|
1140
1431
|
}
|
|
1141
1432
|
if (hasResource) matchingServers.push(serverName);
|
|
@@ -1152,14 +1443,14 @@ const readResourceCommand = new Command("read-resource").description("Read a res
|
|
|
1152
1443
|
else console.log(JSON.stringify(content, null, 2));
|
|
1153
1444
|
await clientManager.disconnectAll();
|
|
1154
1445
|
} catch (error) {
|
|
1155
|
-
console.error(`Error executing read-resource: ${toErrorMessage$
|
|
1446
|
+
console.error(`Error executing read-resource: ${toErrorMessage$3(error)}`);
|
|
1156
1447
|
process.exit(1);
|
|
1157
1448
|
}
|
|
1158
1449
|
});
|
|
1159
1450
|
|
|
1160
1451
|
//#endregion
|
|
1161
1452
|
//#region src/commands/list-prompts.ts
|
|
1162
|
-
function toErrorMessage$
|
|
1453
|
+
function toErrorMessage$2(error) {
|
|
1163
1454
|
return error instanceof Error ? error.message : String(error);
|
|
1164
1455
|
}
|
|
1165
1456
|
const listPromptsCommand = new Command("list-prompts").description("List all available prompts from connected MCP servers").option("-c, --config <path>", "Path to MCP server configuration file").option("-s, --server <name>", "Filter by server name").option("-j, --json", "Output as JSON", false).action(async (options) => {
|
|
@@ -1173,7 +1464,7 @@ const listPromptsCommand = new Command("list-prompts").description("List all ava
|
|
|
1173
1464
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
1174
1465
|
if (!options.json) console.error(`✓ Connected to ${serverName}`);
|
|
1175
1466
|
} catch (error) {
|
|
1176
|
-
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$
|
|
1467
|
+
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$2(error)}`);
|
|
1177
1468
|
}
|
|
1178
1469
|
}));
|
|
1179
1470
|
const clients = options.server ? [clientManager.getClient(options.server)].filter((client) => client !== void 0) : clientManager.getAllClients();
|
|
@@ -1184,7 +1475,7 @@ const listPromptsCommand = new Command("list-prompts").description("List all ava
|
|
|
1184
1475
|
promptsByServer[client.serverName] = await client.listPrompts();
|
|
1185
1476
|
} catch (error) {
|
|
1186
1477
|
promptsByServer[client.serverName] = [];
|
|
1187
|
-
if (!options.json) console.error(`Failed to list prompts from ${client.serverName}: ${toErrorMessage$
|
|
1478
|
+
if (!options.json) console.error(`Failed to list prompts from ${client.serverName}: ${toErrorMessage$2(error)}`);
|
|
1188
1479
|
}
|
|
1189
1480
|
}));
|
|
1190
1481
|
if (options.json) console.log(JSON.stringify(promptsByServer, null, 2));
|
|
@@ -1204,14 +1495,14 @@ const listPromptsCommand = new Command("list-prompts").description("List all ava
|
|
|
1204
1495
|
}
|
|
1205
1496
|
await clientManager.disconnectAll();
|
|
1206
1497
|
} catch (error) {
|
|
1207
|
-
console.error(`Error executing list-prompts: ${toErrorMessage$
|
|
1498
|
+
console.error(`Error executing list-prompts: ${toErrorMessage$2(error)}`);
|
|
1208
1499
|
process.exit(1);
|
|
1209
1500
|
}
|
|
1210
1501
|
});
|
|
1211
1502
|
|
|
1212
1503
|
//#endregion
|
|
1213
1504
|
//#region src/commands/get-prompt.ts
|
|
1214
|
-
function toErrorMessage(error) {
|
|
1505
|
+
function toErrorMessage$1(error) {
|
|
1215
1506
|
return error instanceof Error ? error.message : String(error);
|
|
1216
1507
|
}
|
|
1217
1508
|
const getPromptCommand = new Command("get-prompt").description("Get a prompt by name from a connected MCP server").argument("<promptName>", "Prompt name to fetch").option("-c, --config <path>", "Path to MCP server configuration file").option("-s, --server <name>", "Server name (required if prompt exists on multiple servers)").option("-a, --args <json>", "Prompt arguments as JSON string", "{}").option("-j, --json", "Output as JSON", false).action(async (promptName, options) => {
|
|
@@ -1231,7 +1522,7 @@ const getPromptCommand = new Command("get-prompt").description("Get a prompt by
|
|
|
1231
1522
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
1232
1523
|
if (!options.json) console.error(`✓ Connected to ${serverName}`);
|
|
1233
1524
|
} catch (error) {
|
|
1234
|
-
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage(error)}`);
|
|
1525
|
+
if (!options.json) console.error(`✗ Failed to connect to ${serverName}: ${toErrorMessage$1(error)}`);
|
|
1235
1526
|
}
|
|
1236
1527
|
}));
|
|
1237
1528
|
const clients = clientManager.getAllClients();
|
|
@@ -1254,7 +1545,7 @@ const getPromptCommand = new Command("get-prompt").description("Get a prompt by
|
|
|
1254
1545
|
try {
|
|
1255
1546
|
if ((await client$1.listPrompts()).some((prompt$1) => prompt$1.name === promptName)) matchingServers.push(client$1.serverName);
|
|
1256
1547
|
} catch (error) {
|
|
1257
|
-
if (!options.json) console.error(`Failed to list prompts from ${client$1.serverName}: ${toErrorMessage(error)}`);
|
|
1548
|
+
if (!options.json) console.error(`Failed to list prompts from ${client$1.serverName}: ${toErrorMessage$1(error)}`);
|
|
1258
1549
|
}
|
|
1259
1550
|
}));
|
|
1260
1551
|
if (matchingServers.length === 0) throw new Error(`Prompt "${promptName}" not found on any connected server`);
|
|
@@ -1270,7 +1561,7 @@ const getPromptCommand = new Command("get-prompt").description("Get a prompt by
|
|
|
1270
1561
|
}
|
|
1271
1562
|
await clientManager.disconnectAll();
|
|
1272
1563
|
} catch (error) {
|
|
1273
|
-
console.error(`Error executing get-prompt: ${toErrorMessage(error)}`);
|
|
1564
|
+
console.error(`Error executing get-prompt: ${toErrorMessage$1(error)}`);
|
|
1274
1565
|
process.exit(1);
|
|
1275
1566
|
}
|
|
1276
1567
|
});
|
|
@@ -1400,6 +1691,52 @@ const prefetchCommand = new Command("prefetch").description("Pre-download packag
|
|
|
1400
1691
|
}
|
|
1401
1692
|
});
|
|
1402
1693
|
|
|
1694
|
+
//#endregion
|
|
1695
|
+
//#region src/commands/stop.ts
|
|
1696
|
+
/**
|
|
1697
|
+
* Stop Command
|
|
1698
|
+
*
|
|
1699
|
+
* Stops a running HTTP one-mcp server using the authenticated admin endpoint
|
|
1700
|
+
* and the persisted runtime registry.
|
|
1701
|
+
*/
|
|
1702
|
+
function toErrorMessage(error) {
|
|
1703
|
+
return error instanceof Error ? error.message : String(error);
|
|
1704
|
+
}
|
|
1705
|
+
function printStopResult(result) {
|
|
1706
|
+
console.log(`Stopped one-mcp server '${result.serverId}'.`);
|
|
1707
|
+
console.log(`Endpoint: http://${result.host}:${result.port}`);
|
|
1708
|
+
console.log(`Result: ${result.message}`);
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Stop a running HTTP one-mcp server.
|
|
1712
|
+
*/
|
|
1713
|
+
const stopCommand = new Command("stop").description("Stop a running HTTP one-mcp server").option("--id <id>", "Target server ID from the runtime registry").option("--host <host>", "Target runtime host").option("--port <port>", "Target runtime port", (value) => Number.parseInt(value, 10)).option("-c, --config <path>", "Reserved for future config-based targeting support").option("--token <token>", "Override the persisted shutdown token").option("--force", "Skip server ID verification against the /health response", false).option("-j, --json", "Output as JSON", false).option("--timeout <ms>", "Maximum time to wait for shutdown completion", (value) => Number.parseInt(value, 10), 5e3).action(async (options) => {
|
|
1714
|
+
try {
|
|
1715
|
+
if (options.config) console.error("Warning: --config is not used yet; runtime resolution uses the persisted registry.");
|
|
1716
|
+
const result = await new StopServerService().stop({
|
|
1717
|
+
serverId: options.id,
|
|
1718
|
+
host: options.host,
|
|
1719
|
+
port: options.port,
|
|
1720
|
+
token: options.token,
|
|
1721
|
+
force: options.force,
|
|
1722
|
+
timeoutMs: options.timeout
|
|
1723
|
+
});
|
|
1724
|
+
if (options.json) {
|
|
1725
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
printStopResult(result);
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
const errorMessage = `Error executing stop: ${toErrorMessage(error)}`;
|
|
1731
|
+
if (options.json) console.log(JSON.stringify({
|
|
1732
|
+
ok: false,
|
|
1733
|
+
error: errorMessage
|
|
1734
|
+
}, null, 2));
|
|
1735
|
+
else console.error(errorMessage);
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1403
1740
|
//#endregion
|
|
1404
1741
|
//#region src/cli.ts
|
|
1405
1742
|
/**
|
|
@@ -1437,6 +1774,7 @@ async function main() {
|
|
|
1437
1774
|
program.addCommand(listPromptsCommand);
|
|
1438
1775
|
program.addCommand(getPromptCommand);
|
|
1439
1776
|
program.addCommand(prefetchCommand);
|
|
1777
|
+
program.addCommand(stopCommand);
|
|
1440
1778
|
await program.parseAsync(process.argv);
|
|
1441
1779
|
} catch (error) {
|
|
1442
1780
|
console.error(`CLI execution failed: ${error instanceof Error ? error.message : error}`);
|