cloudflare-mcp-smart-proxy 1.5.9 → 1.5.10
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/package.json +1 -1
- package/src/memory-schema.js +6 -1
- package/src/reference-connectors.js +36 -7
- package/src/router.js +12 -27
package/package.json
CHANGED
package/src/memory-schema.js
CHANGED
|
@@ -40,7 +40,12 @@ function normalizeStringArray(value, limit = 32) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function assertNoSensitiveMemory(input = {}) {
|
|
43
|
-
const text = [
|
|
43
|
+
const text = [
|
|
44
|
+
input.title,
|
|
45
|
+
input.content,
|
|
46
|
+
...(input.tags || []),
|
|
47
|
+
...(input.evidenceRefs || input.evidence_refs || [])
|
|
48
|
+
]
|
|
44
49
|
.filter(Boolean).join('\n');
|
|
45
50
|
if (SENSITIVE_MEMORY_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
46
51
|
throw new Error('Memory content appears to contain a secret or approval credential');
|
|
@@ -44,19 +44,40 @@ function ensureDir(targetPath) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
47
|
+
function isPathWithin(rootPath, candidatePath) {
|
|
48
|
+
const relative = path.relative(rootPath, candidatePath);
|
|
49
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveAtomicWriteTarget(filePath, { allowedRoot = null, followFinalSymlink = true } = {}) {
|
|
53
|
+
const resolvedFilePath = path.resolve(filePath);
|
|
54
|
+
const resolvedAllowedRoot = allowedRoot ? fs.realpathSync(path.resolve(allowedRoot)) : null;
|
|
55
|
+
if (resolvedAllowedRoot) {
|
|
56
|
+
const physicalParent = fs.realpathSync(path.dirname(resolvedFilePath));
|
|
57
|
+
if (!isPathWithin(resolvedAllowedRoot, physicalParent)) {
|
|
58
|
+
throw new Error(`Connector configuration target escapes the selected workspace: ${filePath}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
try {
|
|
49
|
-
if (fs.lstatSync(
|
|
50
|
-
|
|
63
|
+
if (fs.lstatSync(resolvedFilePath).isSymbolicLink()) {
|
|
64
|
+
if (!followFinalSymlink) {
|
|
65
|
+
throw new Error(`Connector configuration target must not be a symbolic link: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
const physicalTarget = fs.realpathSync(resolvedFilePath);
|
|
68
|
+
if (resolvedAllowedRoot && !isPathWithin(resolvedAllowedRoot, physicalTarget)) {
|
|
69
|
+
throw new Error(`Connector configuration target escapes the selected workspace: ${filePath}`);
|
|
70
|
+
}
|
|
71
|
+
return physicalTarget;
|
|
51
72
|
}
|
|
52
73
|
} catch (error) {
|
|
53
74
|
if (error?.code !== 'ENOENT') throw error;
|
|
54
75
|
}
|
|
55
|
-
return
|
|
76
|
+
return resolvedFilePath;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
|
-
export function writeFileAtomically(filePath, content) {
|
|
59
|
-
const writeTarget = resolveAtomicWriteTarget(filePath);
|
|
79
|
+
export function writeFileAtomically(filePath, content, options = {}) {
|
|
80
|
+
const writeTarget = resolveAtomicWriteTarget(filePath, options);
|
|
60
81
|
ensureDir(writeTarget);
|
|
61
82
|
const temporaryPath = `${writeTarget}.${process.pid}.${Date.now()}.tmp`;
|
|
62
83
|
try {
|
|
@@ -65,6 +86,7 @@ export function writeFileAtomically(filePath, content) {
|
|
|
65
86
|
flag: 'wx',
|
|
66
87
|
mode: 0o600
|
|
67
88
|
});
|
|
89
|
+
resolveAtomicWriteTarget(filePath, options);
|
|
68
90
|
fs.renameSync(temporaryPath, writeTarget);
|
|
69
91
|
} catch (error) {
|
|
70
92
|
if (fs.existsSync(temporaryPath)) fs.unlinkSync(temporaryPath);
|
|
@@ -367,6 +389,13 @@ export function installReferenceConnector({
|
|
|
367
389
|
runtime
|
|
368
390
|
});
|
|
369
391
|
|
|
392
|
+
const writeOptions = target.scope === 'project'
|
|
393
|
+
? { allowedRoot: workspaceRoot, followFinalSymlink: false }
|
|
394
|
+
: {};
|
|
395
|
+
if (target.scope === 'project') {
|
|
396
|
+
resolveAtomicWriteTarget(target.targetFile, writeOptions);
|
|
397
|
+
}
|
|
398
|
+
|
|
370
399
|
let renderedContent = '';
|
|
371
400
|
if (target.format === 'toml') {
|
|
372
401
|
const existingContent = fs.existsSync(target.targetFile)
|
|
@@ -383,7 +412,7 @@ export function installReferenceConnector({
|
|
|
383
412
|
}
|
|
384
413
|
|
|
385
414
|
if (!dryRun) {
|
|
386
|
-
writeFileAtomically(target.targetFile, renderedContent);
|
|
415
|
+
writeFileAtomically(target.targetFile, renderedContent, writeOptions);
|
|
387
416
|
}
|
|
388
417
|
|
|
389
418
|
return {
|
package/src/router.js
CHANGED
|
@@ -45,7 +45,6 @@ export class SmartRouter {
|
|
|
45
45
|
this.workspaceRoot = workspaceRoot || process.cwd();
|
|
46
46
|
this.deviceIdentity = deviceIdentity;
|
|
47
47
|
this.getScopeHeaders = typeof getScopeHeaders === 'function' ? getScopeHeaders : () => ({});
|
|
48
|
-
|
|
49
48
|
// 工具路由规则
|
|
50
49
|
this.routingRules = {
|
|
51
50
|
// 本地工具(需要文件系统访问)
|
|
@@ -136,30 +135,22 @@ export class SmartRouter {
|
|
|
136
135
|
return 'cloud';
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
getWorkspaceWarnings() {
|
|
140
|
-
const resolution = this.localTools?.connectorBridge?.state?.workspaceResolution;
|
|
141
|
-
if (resolution?.workspaceId) return [];
|
|
142
|
-
return Array.isArray(resolution?.warnings) ? resolution.warnings : [];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
138
|
/**
|
|
146
139
|
* 执行工具调用
|
|
147
140
|
*/
|
|
148
141
|
async executeTool(toolName, params) {
|
|
149
142
|
const route = this.routeTool(toolName);
|
|
150
|
-
|
|
151
|
-
if (route === 'cloud' && workspaceWarnings.length) {
|
|
152
|
-
return {
|
|
153
|
-
objectType: 'connector_workspace_warning',
|
|
154
|
-
executed: false,
|
|
155
|
-
warnings: workspaceWarnings
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
143
|
+
|
|
159
144
|
if (route === 'local') {
|
|
160
145
|
return await this.localTools.execute(toolName, params);
|
|
161
146
|
} else {
|
|
162
|
-
|
|
147
|
+
const recoveryParams = toolName === 'request_project_workspace_registration'
|
|
148
|
+
? {
|
|
149
|
+
...params,
|
|
150
|
+
projectIdentity: this.localTools?.connectorBridge?.state?.projectIdentity
|
|
151
|
+
}
|
|
152
|
+
: params;
|
|
153
|
+
return await this.callCloudTool(toolName, recoveryParams);
|
|
163
154
|
}
|
|
164
155
|
}
|
|
165
156
|
|
|
@@ -187,7 +178,7 @@ export class SmartRouter {
|
|
|
187
178
|
/**
|
|
188
179
|
* 调用云端工具
|
|
189
180
|
*/
|
|
190
|
-
async callCloudTool(toolName, params) {
|
|
181
|
+
async callCloudTool(toolName, params, { workspaceId = null } = {}) {
|
|
191
182
|
// 对于 skill_* 工具,自动注入项目根路径信息
|
|
192
183
|
if (toolName.startsWith('skill_')) {
|
|
193
184
|
// 如果参数中没有 project_root 且没有 paths,自动添加 project_root
|
|
@@ -231,6 +222,7 @@ export class SmartRouter {
|
|
|
231
222
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
232
223
|
'Content-Type': 'application/json',
|
|
233
224
|
...this.getScopeHeaders(),
|
|
225
|
+
...(workspaceId ? { 'X-CloudMCP-Workspace-ID': workspaceId } : {}),
|
|
234
226
|
...signedHeaders
|
|
235
227
|
},
|
|
236
228
|
body
|
|
@@ -302,14 +294,6 @@ export class SmartRouter {
|
|
|
302
294
|
* 获取所有工具列表(合并云端和本地)
|
|
303
295
|
*/
|
|
304
296
|
async getAllTools() {
|
|
305
|
-
const workspaceWarnings = this.getWorkspaceWarnings();
|
|
306
|
-
if (workspaceWarnings.length) {
|
|
307
|
-
const localTools = await this.localTools.listTools();
|
|
308
|
-
const recoveryTools = new Set(['connector_bridge_status', 'connector_discover_project_probe']);
|
|
309
|
-
return localTools
|
|
310
|
-
.filter((tool) => recoveryTools.has(tool.name))
|
|
311
|
-
.map((tool) => ({ ...tool, source: 'local' }));
|
|
312
|
-
}
|
|
313
297
|
const [cloudTools, localTools] = await Promise.all([
|
|
314
298
|
this.getCloudTools(),
|
|
315
299
|
this.localTools.listTools()
|
|
@@ -334,7 +318,7 @@ export class SmartRouter {
|
|
|
334
318
|
/**
|
|
335
319
|
* 获取云端工具列表
|
|
336
320
|
*/
|
|
337
|
-
async getCloudTools() {
|
|
321
|
+
async getCloudTools({ workspaceId = null } = {}) {
|
|
338
322
|
const body = JSON.stringify({
|
|
339
323
|
jsonrpc: '2.0',
|
|
340
324
|
id: Date.now(),
|
|
@@ -350,6 +334,7 @@ export class SmartRouter {
|
|
|
350
334
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
351
335
|
'Content-Type': 'application/json',
|
|
352
336
|
...this.getScopeHeaders(),
|
|
337
|
+
...(workspaceId ? { 'X-CloudMCP-Workspace-ID': workspaceId } : {}),
|
|
353
338
|
...signedHeaders
|
|
354
339
|
},
|
|
355
340
|
body
|