cloudflare-mcp-smart-proxy 1.4.1 → 1.4.2
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/connector-cli.js +10 -5
- package/package.json +4 -2
- package/src/local-tools.js +2 -2
- package/src/tools/command-executor.js +26 -12
- package/src/tools/file-operations.js +20 -6
package/connector-cli.js
CHANGED
|
@@ -29,6 +29,7 @@ function printUsage() {
|
|
|
29
29
|
console.error(' --app-server-socket <path> Current runtime Codex app-server socket');
|
|
30
30
|
console.error(' --runtime-id <id> Current Codex runtime identity');
|
|
31
31
|
console.error(' --dry-run Show output without writing');
|
|
32
|
+
console.error(' --show-secrets Explicitly include credentials in print output');
|
|
32
33
|
console.error('');
|
|
33
34
|
console.error('The reload codex command uses the existing Codex config and requires no credentials.');
|
|
34
35
|
}
|
|
@@ -47,8 +48,8 @@ function parseArgs(argv) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const key = token.slice(2);
|
|
50
|
-
if (key === 'dry-run') {
|
|
51
|
-
options
|
|
51
|
+
if (key === 'dry-run' || key === 'show-secrets') {
|
|
52
|
+
options[key === 'dry-run' ? 'dryRun' : key] = true;
|
|
52
53
|
continue;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -95,7 +96,7 @@ function buildInstallOptions(options) {
|
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
function printInstallResult(result, { includeContent = false } = {}) {
|
|
99
|
+
function printInstallResult(result, { includeContent = false, includeSecrets = false } = {}) {
|
|
99
100
|
console.error(`ecosystem: ${result.ecosystem}`);
|
|
100
101
|
console.error(`scope: ${result.scope}`);
|
|
101
102
|
console.error(`target: ${result.targetFile}`);
|
|
@@ -111,7 +112,11 @@ function printInstallResult(result, { includeContent = false } = {}) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
if (includeContent) {
|
|
114
|
-
|
|
115
|
+
const apiKey = result.serverDefinition?.env?.CLOUDFLARE_MCP_API_KEY || '';
|
|
116
|
+
const renderedContent = includeSecrets
|
|
117
|
+
? result.renderedContent
|
|
118
|
+
: (apiKey ? result.renderedContent.split(apiKey).join('[REDACTED]') : result.renderedContent);
|
|
119
|
+
process.stdout.write(`${renderedContent}\n`);
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -177,7 +182,7 @@ async function main() {
|
|
|
177
182
|
|
|
178
183
|
if (options.command === 'print') {
|
|
179
184
|
const result = printReferenceConnectorConfig(buildInstallOptions(options));
|
|
180
|
-
printInstallResult(result, { includeContent: true });
|
|
185
|
+
printInstallResult(result, { includeContent: true, includeSecrets: options['show-secrets'] === true });
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
188
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-mcp-smart-proxy",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"@hono/node-server": "2.0.12",
|
|
37
37
|
"ajv": "8.20.0",
|
|
38
38
|
"body-parser": "2.3.0",
|
|
39
|
-
"fast-uri": "3.1.
|
|
39
|
+
"fast-uri": "3.1.5",
|
|
40
|
+
"hono": "4.12.34",
|
|
41
|
+
"ip-address": "10.4.0",
|
|
40
42
|
"path-to-regexp": "8.4.0",
|
|
41
43
|
"qs": "6.15.3"
|
|
42
44
|
}
|
package/src/local-tools.js
CHANGED
|
@@ -214,13 +214,13 @@ export class LocalToolExecutor {
|
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
name: 'execute_command',
|
|
217
|
-
description: 'Execute a shell
|
|
217
|
+
description: 'Execute a local process without shell interpretation',
|
|
218
218
|
inputSchema: {
|
|
219
219
|
type: 'object',
|
|
220
220
|
properties: {
|
|
221
221
|
command: {
|
|
222
222
|
type: 'string',
|
|
223
|
-
description: '
|
|
223
|
+
description: 'Executable and arguments to run'
|
|
224
224
|
},
|
|
225
225
|
cwd: {
|
|
226
226
|
type: 'string',
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import { FileOperations } from './file-operations.js';
|
|
8
|
+
|
|
9
|
+
function parseCommand(command) {
|
|
10
|
+
const tokens = [];
|
|
11
|
+
const pattern = /"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s]+)/g;
|
|
12
|
+
let match;
|
|
13
|
+
while ((match = pattern.exec(command)) !== null) {
|
|
14
|
+
tokens.push((match[1] ?? match[2] ?? match[3]).replace(/\\([\\"])/g, '$1'));
|
|
15
|
+
}
|
|
16
|
+
return tokens;
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
export class CommandExecutor {
|
|
9
20
|
/**
|
|
@@ -16,17 +27,21 @@ export class CommandExecutor {
|
|
|
16
27
|
throw new Error('command parameter is required');
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
const workingDir = cwd ?
|
|
30
|
+
const workingDir = cwd ? FileOperations.validatePath(cwd, workspaceRoot) : FileOperations.validatePath('.', workspaceRoot);
|
|
20
31
|
|
|
21
32
|
return new Promise((resolve, reject) => {
|
|
22
33
|
// 解析命令和参数
|
|
23
|
-
const parts = command.trim()
|
|
34
|
+
const parts = parseCommand(command.trim());
|
|
24
35
|
const cmd = parts[0];
|
|
25
36
|
const args = parts.slice(1);
|
|
37
|
+
if (!cmd) {
|
|
38
|
+
reject(new Error('command parameter must include an executable'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
26
41
|
|
|
27
42
|
// 安全限制:禁止某些危险命令
|
|
28
|
-
const dangerousCommands = ['rm', 'del', 'format', 'shutdown', 'reboot'];
|
|
29
|
-
if (dangerousCommands.
|
|
43
|
+
const dangerousCommands = new Set(['rm', 'del', 'format', 'shutdown', 'reboot']);
|
|
44
|
+
if (dangerousCommands.has(path.basename(cmd).toLowerCase())) {
|
|
30
45
|
reject(new Error(`Command "${cmd}" is not allowed for security reasons`));
|
|
31
46
|
return;
|
|
32
47
|
}
|
|
@@ -38,9 +53,13 @@ export class CommandExecutor {
|
|
|
38
53
|
// 执行命令
|
|
39
54
|
const proc = spawn(cmd, args, {
|
|
40
55
|
cwd: workingDir,
|
|
41
|
-
shell:
|
|
56
|
+
shell: false,
|
|
42
57
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
43
58
|
});
|
|
59
|
+
const timeout = setTimeout(() => {
|
|
60
|
+
proc.kill();
|
|
61
|
+
reject(new Error('Command execution timeout (30s)'));
|
|
62
|
+
}, 30000);
|
|
44
63
|
|
|
45
64
|
// 收集输出
|
|
46
65
|
proc.stdout.on('data', (data) => {
|
|
@@ -53,6 +72,7 @@ export class CommandExecutor {
|
|
|
53
72
|
|
|
54
73
|
// 处理完成
|
|
55
74
|
proc.on('close', (code) => {
|
|
75
|
+
clearTimeout(timeout);
|
|
56
76
|
const duration = Date.now() - startTime;
|
|
57
77
|
|
|
58
78
|
resolve({
|
|
@@ -68,15 +88,9 @@ export class CommandExecutor {
|
|
|
68
88
|
|
|
69
89
|
// 处理错误
|
|
70
90
|
proc.on('error', (error) => {
|
|
91
|
+
clearTimeout(timeout);
|
|
71
92
|
reject(new Error(`Command execution failed: ${error.message}`));
|
|
72
93
|
});
|
|
73
|
-
|
|
74
|
-
// 超时保护(30秒)
|
|
75
|
-
setTimeout(() => {
|
|
76
|
-
proc.kill();
|
|
77
|
-
reject(new Error('Command execution timeout (30s)'));
|
|
78
|
-
}, 30000);
|
|
79
94
|
});
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
|
-
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
|
+
import fsSync from 'fs';
|
|
6
7
|
import path from 'path';
|
|
7
8
|
|
|
8
9
|
export class FileOperations {
|
|
@@ -16,14 +17,28 @@ export class FileOperations {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// 限制在工作目录内
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
const resolvedRoot = fsSync.realpathSync(path.resolve(workspaceRoot));
|
|
21
|
+
const resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
22
|
+
const relativePath = path.relative(resolvedRoot, resolvedPath);
|
|
21
23
|
|
|
22
|
-
if (
|
|
24
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
23
25
|
throw new Error('File path outside workspace root');
|
|
24
26
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
let existingAncestor = resolvedPath;
|
|
29
|
+
while (!fsSync.existsSync(existingAncestor)) {
|
|
30
|
+
const parent = path.dirname(existingAncestor);
|
|
31
|
+
if (parent === existingAncestor) break;
|
|
32
|
+
existingAncestor = parent;
|
|
33
|
+
}
|
|
34
|
+
const realAncestor = fsSync.realpathSync(existingAncestor);
|
|
35
|
+
const realTarget = path.resolve(realAncestor, path.relative(existingAncestor, resolvedPath));
|
|
36
|
+
const realRelativePath = path.relative(resolvedRoot, realTarget);
|
|
37
|
+
if (realRelativePath.startsWith('..') || path.isAbsolute(realRelativePath)) {
|
|
38
|
+
throw new Error('File path resolves outside workspace root');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return realTarget;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
/**
|
|
@@ -208,4 +223,3 @@ export class FileOperations {
|
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
}
|
|
211
|
-
|