Skip to content

Commit 6088827

Browse files
committed
feat(cli): add CLI argument parsing for cloud-mode and integration-ide 🚀
1 parent 4e157b9 commit 6088827

6 files changed

Lines changed: 226 additions & 13 deletions

File tree

mcp/src/cli.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ import { createCloudBaseMcpServer } from "./server.js";
55
import { telemetryReporter, reportToolkitLifecycle } from "./utils/telemetry.js";
66
import { info, warn } from './utils/logger.js';
77

8+
/**
9+
* Parse command line arguments
10+
* Supports --cloud-mode and --integration-ide flags
11+
*/
12+
function parseCommandLineArgs(): {
13+
cloudMode: boolean;
14+
ide?: string;
15+
} {
16+
const args = process.argv.slice(2);
17+
let cloudMode = false;
18+
let ide: string | undefined;
19+
20+
for (let i = 0; i < args.length; i++) {
21+
const arg = args[i];
22+
23+
if (arg === '--cloud-mode') {
24+
cloudMode = true;
25+
} else if (arg === '--integration-ide' && i + 1 < args.length) {
26+
ide = args[i + 1];
27+
i++; // Skip the next argument since we consumed it
28+
} else if (arg.startsWith('--integration-ide=')) {
29+
ide = arg.split('=')[1];
30+
}
31+
}
32+
33+
return { cloudMode, ide };
34+
}
35+
836
// 劫持 console.log/info/warn,防止污染 stdout 协议流
937
const joinArgs = (...args: any[]) => args.map(a => {
1038
if (typeof a === 'string') return a;
@@ -28,11 +56,24 @@ const startTime = Date.now();
2856
const isTestEnvironment = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
2957
const enableTelemetry = !isTestEnvironment;
3058

31-
// Create server instance with conditional telemetry
59+
// Parse command line arguments
60+
const { cloudMode, ide } = parseCommandLineArgs();
61+
62+
// Log startup information
63+
if (cloudMode) {
64+
info("Starting CloudBase MCP Server in cloud mode");
65+
}
66+
if (ide) {
67+
info(`Integration IDE: ${ide}`);
68+
}
69+
70+
// Create server instance with conditional telemetry and CLI options
3271
const server = createCloudBaseMcpServer({
3372
name: "cloudbase-mcp",
3473
version: "1.0.0",
35-
enableTelemetry
74+
enableTelemetry,
75+
cloudMode,
76+
ide
3677
});
3778

3879
async function main() {
@@ -43,7 +84,8 @@ async function main() {
4384
// 上报启动信息
4485
if (telemetryReporter.isEnabled()) {
4586
await reportToolkitLifecycle({
46-
event: 'start'
87+
event: 'start',
88+
ide
4789
});
4890
}
4991
}
@@ -57,7 +99,8 @@ function setupExitHandlers() {
5799
event: 'exit',
58100
duration,
59101
exitCode,
60-
error: signal ? `Process terminated by signal: ${signal}` : undefined
102+
error: signal ? `Process terminated by signal: ${signal}` : undefined,
103+
ide
61104
});
62105
}
63106
};
@@ -105,7 +148,8 @@ main().catch(async (error) => {
105148
event: 'exit',
106149
duration,
107150
exitCode: 1,
108-
error: `Startup failed: ${error.message}`
151+
error: `Startup failed: ${error.message}`,
152+
ide
109153
});
110154
}
111155

mcp/src/server.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { wrapServerWithTelemetry } from "./utils/tool-wrapper.js";
1414
import { registerGatewayTools } from "./tools/gateway.js";
1515
import { registerInviteCodeTools } from "./tools/invite-code.js";
1616
import { CloudBaseOptions } from "./types.js";
17+
import { enableCloudMode } from "./utils/cloud-mode.js";
1718

1819

1920
// 插件定义
@@ -71,6 +72,7 @@ function parseEnabledPlugins(): string[] {
7172
// 扩展 McpServer 类型以包含 cloudBaseOptions 和新的registerTool方法
7273
export interface ExtendedMcpServer extends McpServer {
7374
cloudBaseOptions?: CloudBaseOptions;
75+
ide?: string;
7476
}
7577

7678
/**
@@ -98,14 +100,23 @@ export function createCloudBaseMcpServer(options?: {
98100
version?: string;
99101
enableTelemetry?: boolean;
100102
cloudBaseOptions?: CloudBaseOptions;
103+
cloudMode?: boolean;
104+
ide?: string;
101105
}): ExtendedMcpServer {
102106
const {
103107
name = "cloudbase-mcp",
104108
version = "1.0.0",
105109
enableTelemetry = true,
106-
cloudBaseOptions
110+
cloudBaseOptions,
111+
cloudMode = false,
112+
ide
107113
} = options ?? {};
108114

115+
// Enable cloud mode if specified
116+
if (cloudMode) {
117+
enableCloudMode();
118+
}
119+
109120
// Create server instance
110121
const server = new McpServer({
111122
name,
@@ -122,6 +133,11 @@ export function createCloudBaseMcpServer(options?: {
122133
server.cloudBaseOptions = cloudBaseOptions;
123134
}
124135

136+
// Store ide in server instance for telemetry
137+
if (ide) {
138+
server.ide = ide;
139+
}
140+
125141
// Enable telemetry if requested
126142
if (enableTelemetry) {
127143
wrapServerWithTelemetry(server);

mcp/src/utils/cloud-mode.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,23 @@ import { debug } from './logger.js';
88
* 3. Environment variable MCP_CLOUD_MODE=true
99
*/
1010
export function isCloudMode(): boolean {
11+
// Check for CLI argument first
12+
const hasCloudModeArg = process.argv.includes('--cloud-mode');
13+
14+
// Check environment variables
1115
const cloudModeEnabled = process.env.CLOUDBASE_MCP_CLOUD_MODE === 'true' ||
1216
process.env.MCP_CLOUD_MODE === 'true';
1317

14-
if (cloudModeEnabled) {
15-
debug('Cloud mode is enabled');
18+
const isEnabled = hasCloudModeArg || cloudModeEnabled;
19+
20+
if (isEnabled) {
21+
debug('Cloud mode is enabled', {
22+
source: hasCloudModeArg ? 'CLI_ARG' : 'ENV_VAR',
23+
envVar: process.env.CLOUDBASE_MCP_CLOUD_MODE || process.env.MCP_CLOUD_MODE
24+
});
1625
}
1726

18-
return cloudModeEnabled;
27+
return isEnabled;
1928
}
2029

2130
/**
@@ -33,6 +42,11 @@ export function getCloudModeStatus(): {
3342
enabled: boolean;
3443
source: string | null;
3544
} {
45+
// Check CLI argument first
46+
if (process.argv.includes('--cloud-mode')) {
47+
return { enabled: true, source: 'CLI_ARG' };
48+
}
49+
3650
if (process.env.CLOUDBASE_MCP_CLOUD_MODE === 'true') {
3751
return { enabled: true, source: 'CLOUDBASE_MCP_CLOUD_MODE' };
3852
}

mcp/src/utils/telemetry.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ export const reportToolCall = async (params: {
232232
error?: string;
233233
inputParams?: any; // 入参上报
234234
cloudBaseOptions?: CloudBaseOptions; // 新增:CloudBase 配置选项
235+
ide?: string; // 新增:集成IDE信息
235236
}) => {
236237
const {
237238
nodeVersion,
@@ -283,6 +284,11 @@ export const reportToolCall = async (params: {
283284
}
284285
}
285286

287+
// 添加集成IDE信息(如果提供)
288+
if (params.ide) {
289+
eventData.ide = params.ide;
290+
}
291+
286292
telemetryReporter.report('toolkit_tool_call', eventData);
287293
};
288294

@@ -293,6 +299,7 @@ export const reportToolkitLifecycle = async (params: {
293299
exitCode?: number; // 对于 exit 事件,表示退出码
294300
error?: string; // 对于异常退出
295301
cloudBaseOptions?: CloudBaseOptions; // 新增:CloudBase 配置选项
302+
ide?: string; // 新增:集成IDE信息
296303
}) => {
297304
const {
298305
nodeVersion,
@@ -330,5 +337,10 @@ export const reportToolkitLifecycle = async (params: {
330337
mcpVersion
331338
};
332339

340+
// 添加集成IDE信息(如果提供)
341+
if (params.ide) {
342+
eventData.ide = params.ide;
343+
}
344+
333345
telemetryReporter.report('toolkit_lifecycle', eventData);
334346
};

mcp/src/utils/tool-wrapper.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { debug } from './logger.js';
55
import { CloudBaseOptions } from '../types.js';
66
import os from 'os';
77

8+
// 扩展 McpServer 类型以包含 ide
9+
interface ExtendedMcpServer extends McpServer {
10+
cloudBaseOptions?: CloudBaseOptions;
11+
ide?: string;
12+
}
13+
814
/**
915
* 工具包装器,为 MCP 工具添加数据上报功能
1016
* 自动记录工具调用的成功/失败状态、执行时长等信息
@@ -72,7 +78,7 @@ ${JSON.stringify(sanitizeArgs(args), null, 2)}
7278
/**
7379
* 创建包装后的处理函数,添加数据上报功能
7480
*/
75-
function createWrappedHandler(name: string, handler: any, cloudBaseOptions?: CloudBaseOptions) {
81+
function createWrappedHandler(name: string, handler: any, server: ExtendedMcpServer) {
7682
return async (args: any) => {
7783
const startTime = Date.now();
7884
let success = false;
@@ -125,7 +131,8 @@ function createWrappedHandler(name: string, handler: any, cloudBaseOptions?: Clo
125131
duration,
126132
error: errorMessage,
127133
inputParams: sanitizeArgs(args), // 添加入参上报
128-
cloudBaseOptions // 传递 CloudBase 配置
134+
cloudBaseOptions: server.cloudBaseOptions, // 传递 CloudBase 配置
135+
ide: server.ide || process.env.INTEGRATION_IDE // 传递集成IDE信息
129136
});
130137
}
131138
};
@@ -147,8 +154,8 @@ export function wrapServerWithTelemetry(server: McpServer): void {
147154
toolConfig
148155
});
149156

150-
// 使用包装后的处理函数,传递服务器配置
151-
const wrappedHandler = createWrappedHandler(toolName, handler, (server as any).cloudBaseOptions);
157+
// 使用包装后的处理函数,传递服务器实例
158+
const wrappedHandler = createWrappedHandler(toolName, handler, server as ExtendedMcpServer);
152159

153160
// 调用原始 registerTool 方法
154161
return originalRegisterTool(toolName, toolConfig, wrappedHandler);

tests/cli-args.test.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { describe, test, expect } from 'vitest';
2+
import { spawn } from 'child_process';
3+
import path from 'path';
4+
import { fileURLToPath } from 'url';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
9+
describe('CLI Arguments', () => {
10+
const cliPath = path.join(__dirname, '../mcp/dist/cli.cjs');
11+
12+
test('should parse --cloud-mode argument', () => {
13+
return new Promise((resolve, reject) => {
14+
const child = spawn('node', [cliPath, '--cloud-mode'], {
15+
stdio: ['pipe', 'pipe', 'pipe']
16+
});
17+
18+
let output = '';
19+
child.stdout.on('data', (data) => {
20+
output += data.toString();
21+
});
22+
23+
child.stderr.on('data', (data) => {
24+
output += data.toString();
25+
});
26+
27+
child.on('close', (code) => {
28+
expect(output).toContain('Starting CloudBase MCP Server in cloud mode');
29+
resolve();
30+
});
31+
32+
// Send SIGTERM after a short delay to terminate the process
33+
setTimeout(() => {
34+
child.kill('SIGTERM');
35+
}, 1000);
36+
});
37+
});
38+
39+
test('should parse --integration-ide argument', () => {
40+
return new Promise((resolve, reject) => {
41+
const child = spawn('node', [cliPath, '--integration-ide', 'test-ide'], {
42+
stdio: ['pipe', 'pipe', 'pipe']
43+
});
44+
45+
let output = '';
46+
child.stdout.on('data', (data) => {
47+
output += data.toString();
48+
});
49+
50+
child.stderr.on('data', (data) => {
51+
output += data.toString();
52+
});
53+
54+
child.on('close', (code) => {
55+
expect(output).toContain('Integration IDE: test-ide');
56+
resolve();
57+
});
58+
59+
// Send SIGTERM after a short delay to terminate the process
60+
setTimeout(() => {
61+
child.kill('SIGTERM');
62+
}, 1000);
63+
});
64+
});
65+
66+
test('should parse --integration-ide=value argument', () => {
67+
return new Promise((resolve, reject) => {
68+
const child = spawn('node', [cliPath, '--integration-ide=test-ide-equals'], {
69+
stdio: ['pipe', 'pipe', 'pipe']
70+
});
71+
72+
let output = '';
73+
child.stdout.on('data', (data) => {
74+
output += data.toString();
75+
});
76+
77+
child.stderr.on('data', (data) => {
78+
output += data.toString();
79+
});
80+
81+
child.on('close', (code) => {
82+
expect(output).toContain('Integration IDE: test-ide-equals');
83+
resolve();
84+
});
85+
86+
// Send SIGTERM after a short delay to terminate the process
87+
setTimeout(() => {
88+
child.kill('SIGTERM');
89+
}, 1000);
90+
});
91+
});
92+
93+
test('should parse both --cloud-mode and --integration-ide arguments', () => {
94+
return new Promise((resolve, reject) => {
95+
const child = spawn('node', [cliPath, '--cloud-mode', '--integration-ide', 'test-ide'], {
96+
stdio: ['pipe', 'pipe', 'pipe']
97+
});
98+
99+
let output = '';
100+
child.stdout.on('data', (data) => {
101+
output += data.toString();
102+
});
103+
104+
child.stderr.on('data', (data) => {
105+
output += data.toString();
106+
});
107+
108+
child.on('close', (code) => {
109+
expect(output).toContain('Starting CloudBase MCP Server in cloud mode');
110+
expect(output).toContain('Integration IDE: test-ide');
111+
resolve();
112+
});
113+
114+
// Send SIGTERM after a short delay to terminate the process
115+
setTimeout(() => {
116+
child.kill('SIGTERM');
117+
}, 1000);
118+
});
119+
});
120+
});

0 commit comments

Comments
 (0)