cloudflare-mcp-smart-proxy 1.5.5 → 1.5.6
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 +3 -1
- package/package.json +1 -1
- package/src/local-memory-coordinator.js +71 -14
- package/src/local-tools.js +40 -1
- package/src/memory-schema.js +27 -3
- package/src/router.js +7 -2
package/README.md
CHANGED
|
@@ -114,8 +114,10 @@ npx -y -p cloudflare-mcp-smart-proxy cloudmcp-connector activate codex \
|
|
|
114
114
|
- `get_local_memory_target`:读取当前连接器工作空间的本机目标契约。
|
|
115
115
|
- `verify_local_memory_target`:回读验证目标契约或一个精确条目。
|
|
116
116
|
- `capture_memory`:以同一事务写入受治理的 CloudMCP memory plane 和已配置本地目标,双端回读一致后才返回成功。
|
|
117
|
+
- `invalidate_memory`:以 revision、原因和证据软失效一个精确条目,并把同一 canonical JSON 提交到本地与云端。
|
|
118
|
+
- `close_session`:把简洁交接转换为 `working + handoff`,复用与 `capture_memory` 相同的双端事务。
|
|
117
119
|
|
|
118
|
-
`capture_memory`
|
|
120
|
+
`capture_memory / invalidate_memory / close_session` 只有在当前连接器通过云端鉴权、对应工具能力授权及精确 `memory_scope` 资源授权后才会被本地协调器暴露。它们仍复用原代理的 API Key、设备签名和 CloudMCP 请求路由,不存在匿名云端写入口。`search_memory / get_memory_entry / load_context` 保持受治理云端只读工具,由同一授权链提供。
|
|
119
121
|
|
|
120
122
|
本地目录不是硬编码的。以 SoloMap 为目标时,用户或 Agent 可把其记忆根目录显式传给配置工具;未安装 CloudMCP 的 SoloMap 用户继续使用原有 Markdown 机制,公共 SoloMap 插件不会默认依赖这些工具。
|
|
121
123
|
|
package/package.json
CHANGED
|
@@ -10,13 +10,6 @@ import {
|
|
|
10
10
|
validateMemoryIdentifier
|
|
11
11
|
} from './memory-schema.js';
|
|
12
12
|
|
|
13
|
-
const SENSITIVE_MEMORY_PATTERNS = [
|
|
14
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
|
|
15
|
-
/\bCLOUDMCP_APPROVAL_CODE\s*=/i,
|
|
16
|
-
/\b(?:api[_ -]?key|access[_ -]?token|client[_ -]?secret|private[_ -]?key)\s*[:=]\s*\S{12,}/i,
|
|
17
|
-
/\bmcp_[A-Za-z0-9]{16,}\b/
|
|
18
|
-
];
|
|
19
|
-
|
|
20
13
|
function writeJsonAtomically(filePath, value, mode = 0o600) {
|
|
21
14
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
22
15
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -65,12 +58,6 @@ function currentGitCommit(workspaceRoot) {
|
|
|
65
58
|
}
|
|
66
59
|
}
|
|
67
60
|
|
|
68
|
-
function assertNoSensitiveMemory(input) {
|
|
69
|
-
const text = [input.title, input.content, ...(input.evidenceRefs || [])].filter(Boolean).join('\n');
|
|
70
|
-
if (SENSITIVE_MEMORY_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
71
|
-
throw new Error('Memory content appears to contain a secret or approval credential');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
61
|
|
|
75
62
|
export class LocalMemoryCoordinator {
|
|
76
63
|
constructor(options = {}) {
|
|
@@ -200,7 +187,6 @@ export class LocalMemoryCoordinator {
|
|
|
200
187
|
const contract = this.getContract();
|
|
201
188
|
if (!contract || contract.status !== 'active') throw new Error('Local memory target is not configured');
|
|
202
189
|
const input = validateMemoryCaptureInput(params);
|
|
203
|
-
assertNoSensitiveMemory(input);
|
|
204
190
|
const requestedScope = params.memory_scope_id || params.memoryScopeId || '';
|
|
205
191
|
if (requestedScope && !contract.cloudScopeIds.includes(requestedScope)) {
|
|
206
192
|
throw new Error(`Memory scope is not authorized by the local target contract: ${requestedScope}`);
|
|
@@ -332,4 +318,75 @@ export class LocalMemoryCoordinator {
|
|
|
332
318
|
);
|
|
333
319
|
}
|
|
334
320
|
}
|
|
321
|
+
|
|
322
|
+
async invalidate(params = {}) {
|
|
323
|
+
const contract = this.getContract();
|
|
324
|
+
if (!contract || contract.status !== 'active') throw new Error('Local memory target is not configured');
|
|
325
|
+
const memoryId = validateMemoryIdentifier(params.memory_id || params.memoryId, 'memory_id');
|
|
326
|
+
const requestedScope = params.memory_scope_id || params.memoryScopeId || '';
|
|
327
|
+
if (requestedScope && !contract.cloudScopeIds.includes(requestedScope)) {
|
|
328
|
+
throw new Error(`Memory scope is not authorized by the local target contract: ${requestedScope}`);
|
|
329
|
+
}
|
|
330
|
+
let stage = 'cloud_write';
|
|
331
|
+
try {
|
|
332
|
+
const result = await this.callCloud('invalidate_memory', {
|
|
333
|
+
memory_scope_id: requestedScope || undefined,
|
|
334
|
+
memory_id: memoryId,
|
|
335
|
+
expected_revision: params.expected_revision || params.expectedRevision,
|
|
336
|
+
reason: params.reason,
|
|
337
|
+
evidence_refs: params.evidence_refs || params.evidenceRefs
|
|
338
|
+
});
|
|
339
|
+
if (result?.verified !== true) throw new Error(`Cloud memory did not confirm invalidation readback: ${memoryId}`);
|
|
340
|
+
const entry = assertMemoryEntryIntegrity(result.entry);
|
|
341
|
+
if (!contract.cloudScopeIds.includes(entry.scopeId) || entry.status !== 'invalidated') {
|
|
342
|
+
throw new Error(`CloudMCP returned an invalid memory invalidation: ${memoryId}`);
|
|
343
|
+
}
|
|
344
|
+
stage = 'local_write';
|
|
345
|
+
const entryPath = path.join(contract.directory, 'entries', `${entry.id}.json`);
|
|
346
|
+
writeJsonAtomically(entryPath, entry);
|
|
347
|
+
stage = 'local_readback';
|
|
348
|
+
const localEntry = assertMemoryEntryIntegrity(readJson(entryPath));
|
|
349
|
+
if (canonicalMemoryJson(localEntry) !== canonicalMemoryJson(entry)) {
|
|
350
|
+
throw new Error(`Local memory invalidation readback failed for ${entry.id}`);
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
success: true,
|
|
354
|
+
memoryId: entry.id,
|
|
355
|
+
revision: entry.revision,
|
|
356
|
+
canonicalHash: entry.canonicalHash,
|
|
357
|
+
idempotent: result.idempotent === true,
|
|
358
|
+
local: { persisted: true, verified: true },
|
|
359
|
+
cloud: { persisted: true, verified: true },
|
|
360
|
+
entry
|
|
361
|
+
};
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const cloudStatus = stage === 'cloud_write' ? 'unknown' : 'verified';
|
|
364
|
+
const localStatus = stage === 'cloud_write' ? 'not_started' : (stage === 'local_write' ? 'failed' : 'persisted_unverified');
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Memory invalidation ${memoryId} failed at ${stage} `
|
|
367
|
+
+ `(cloud=${cloudStatus}, local=${localStatus}): ${error?.message || 'unknown error'}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async closeSession(params = {}) {
|
|
373
|
+
const summary = typeof params.summary === 'string' ? params.summary.trim() : '';
|
|
374
|
+
if (!summary) throw new Error('summary is required');
|
|
375
|
+
const nextSteps = (Array.isArray(params.next_steps) ? params.next_steps : [])
|
|
376
|
+
.map((step) => String(step).trim())
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
const content = nextSteps.length
|
|
379
|
+
? `${summary}\n\nNext steps:\n${nextSteps.map((step) => `- ${String(step).trim()}`).join('\n')}`
|
|
380
|
+
: summary;
|
|
381
|
+
const captured = await this.capture({
|
|
382
|
+
memory_scope_id: params.memory_scope_id || params.memoryScopeId,
|
|
383
|
+
layer: 'working',
|
|
384
|
+
kind: 'handoff',
|
|
385
|
+
title: params.title || 'Session handoff',
|
|
386
|
+
content,
|
|
387
|
+
tags: params.tags,
|
|
388
|
+
transaction_id: params.transaction_id || params.transactionId
|
|
389
|
+
});
|
|
390
|
+
return { ...captured, closed: true };
|
|
391
|
+
}
|
|
335
392
|
}
|
package/src/local-tools.js
CHANGED
|
@@ -97,6 +97,14 @@ export class LocalToolExecutor {
|
|
|
97
97
|
case 'capture_memory':
|
|
98
98
|
if (!this.memoryCoordinator) throw new Error('Local memory coordinator is not configured');
|
|
99
99
|
return await this.memoryCoordinator.capture(params || {});
|
|
100
|
+
|
|
101
|
+
case 'invalidate_memory':
|
|
102
|
+
if (!this.memoryCoordinator) throw new Error('Local memory coordinator is not configured');
|
|
103
|
+
return await this.memoryCoordinator.invalidate(params || {});
|
|
104
|
+
|
|
105
|
+
case 'close_session':
|
|
106
|
+
if (!this.memoryCoordinator) throw new Error('Local memory coordinator is not configured');
|
|
107
|
+
return await this.memoryCoordinator.closeSession(params || {});
|
|
100
108
|
|
|
101
109
|
default:
|
|
102
110
|
throw new Error(`Unknown local tool: ${toolName}`);
|
|
@@ -136,7 +144,7 @@ export class LocalToolExecutor {
|
|
|
136
144
|
},
|
|
137
145
|
{
|
|
138
146
|
name: 'capture_memory',
|
|
139
|
-
description: 'Validate one memory and commit the same JSON entry to the configured local target and
|
|
147
|
+
description: 'Validate one governed memory and commit the same canonical JSON entry to the configured local target and CloudMCP scope.',
|
|
140
148
|
inputSchema: {
|
|
141
149
|
type: 'object',
|
|
142
150
|
properties: {
|
|
@@ -158,6 +166,37 @@ export class LocalToolExecutor {
|
|
|
158
166
|
required: ['content']
|
|
159
167
|
}
|
|
160
168
|
},
|
|
169
|
+
{
|
|
170
|
+
name: 'invalidate_memory',
|
|
171
|
+
description: 'Soft-invalidate one governed memory and verify the same canonical result in CloudMCP and the configured local target.',
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
memory_scope_id: { type: 'string' },
|
|
176
|
+
memory_id: { type: 'string' },
|
|
177
|
+
expected_revision: { type: 'number' },
|
|
178
|
+
reason: { type: 'string' },
|
|
179
|
+
evidence_refs: { type: 'array', items: { type: 'string' } }
|
|
180
|
+
},
|
|
181
|
+
required: ['memory_id', 'expected_revision', 'reason', 'evidence_refs']
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'close_session',
|
|
186
|
+
description: 'Persist a working handoff through the same verified local-and-cloud memory transaction.',
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
memory_scope_id: { type: 'string' },
|
|
191
|
+
title: { type: 'string' },
|
|
192
|
+
summary: { type: 'string' },
|
|
193
|
+
next_steps: { type: 'array', items: { type: 'string' } },
|
|
194
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
195
|
+
transaction_id: { type: 'string' }
|
|
196
|
+
},
|
|
197
|
+
required: ['summary']
|
|
198
|
+
}
|
|
199
|
+
},
|
|
161
200
|
{
|
|
162
201
|
name: 'read_file',
|
|
163
202
|
description: 'Read a file from local filesystem',
|
package/src/memory-schema.js
CHANGED
|
@@ -5,6 +5,17 @@ const MEMORY_KINDS = new Set([
|
|
|
5
5
|
'preference', 'rule', 'project_fact', 'decision', 'pattern', 'domain', 'handoff', 'observation'
|
|
6
6
|
]);
|
|
7
7
|
const MEMORY_IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
8
|
+
const MEMORY_KIND_LAYERS = new Map([
|
|
9
|
+
['preference', 'stable'], ['rule', 'stable'], ['project_fact', 'stable'],
|
|
10
|
+
['decision', 'stable'], ['pattern', 'stable'], ['domain', 'stable'],
|
|
11
|
+
['handoff', 'working'], ['observation', 'candidate']
|
|
12
|
+
]);
|
|
13
|
+
const SENSITIVE_MEMORY_PATTERNS = [
|
|
14
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
|
|
15
|
+
/\bCLOUDMCP_APPROVAL_CODE\s*=/i,
|
|
16
|
+
/\b(?:api[_ -]?key|access[_ -]?token|client[_ -]?secret|private[_ -]?key)\s*[:=]\s*\S{12,}/i,
|
|
17
|
+
/\bmcp_[A-Za-z0-9]{16,}\b/
|
|
18
|
+
];
|
|
8
19
|
|
|
9
20
|
function normalizeString(value, fallback = null) {
|
|
10
21
|
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
@@ -28,6 +39,14 @@ function normalizeStringArray(value, limit = 32) {
|
|
|
28
39
|
));
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
export function assertNoSensitiveMemory(input = {}) {
|
|
43
|
+
const text = [input.title, input.content, ...(input.evidenceRefs || input.evidence_refs || [])]
|
|
44
|
+
.filter(Boolean).join('\n');
|
|
45
|
+
if (SENSITIVE_MEMORY_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
46
|
+
throw new Error('Memory content appears to contain a secret or approval credential');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
function sortValue(value) {
|
|
32
51
|
if (Array.isArray(value)) return value.map(sortValue);
|
|
33
52
|
if (!value || typeof value !== 'object') return value;
|
|
@@ -58,15 +77,18 @@ export function validateMemoryCaptureInput(payload = {}) {
|
|
|
58
77
|
if (content.length > 12000) throw new Error('Memory content exceeds 12000 characters');
|
|
59
78
|
if (title && title.length > 240) throw new Error('Memory title exceeds 240 characters');
|
|
60
79
|
const layer = normalizeString(payload.layer, 'candidate');
|
|
80
|
+
if (layer === 'stable' && !normalizeString(payload.kind)) throw new Error('Stable memory requires an explicit kind');
|
|
61
81
|
const kind = normalizeString(payload.kind, layer === 'working' ? 'handoff' : 'observation');
|
|
62
82
|
if (!MEMORY_LAYERS.has(layer)) throw new Error(`Unsupported memory layer: ${layer}`);
|
|
63
83
|
if (!MEMORY_KINDS.has(kind)) throw new Error(`Unsupported memory kind: ${kind}`);
|
|
84
|
+
const requiredLayer = MEMORY_KIND_LAYERS.get(kind);
|
|
85
|
+
if (requiredLayer !== layer) throw new Error(`Memory kind ${kind} must use the ${requiredLayer} layer`);
|
|
64
86
|
const evidenceRefs = normalizeStringArray(payload.evidence_refs || payload.evidenceRefs, 20);
|
|
65
87
|
const verifiedAt = normalizeIsoDate(payload.verified_at || payload.verifiedAt);
|
|
66
|
-
if (layer === 'stable' && evidenceRefs.length === 0
|
|
67
|
-
throw new Error('Stable memory requires
|
|
88
|
+
if (layer === 'stable' && evidenceRefs.length === 0) {
|
|
89
|
+
throw new Error('Stable memory requires at least one evidence_ref');
|
|
68
90
|
}
|
|
69
|
-
|
|
91
|
+
const normalized = {
|
|
70
92
|
title: title || 'Untitled memory',
|
|
71
93
|
content,
|
|
72
94
|
layer,
|
|
@@ -79,6 +101,8 @@ export function validateMemoryCaptureInput(payload = {}) {
|
|
|
79
101
|
supersedes: normalizeStringArray(payload.supersedes, 20)
|
|
80
102
|
.map((id) => validateMemoryIdentifier(id, 'supersedes entry id'))
|
|
81
103
|
};
|
|
104
|
+
assertNoSensitiveMemory(normalized);
|
|
105
|
+
return normalized;
|
|
82
106
|
}
|
|
83
107
|
|
|
84
108
|
export function assertMemoryEntryIntegrity(entry) {
|
package/src/router.js
CHANGED
|
@@ -72,7 +72,9 @@ export class SmartRouter {
|
|
|
72
72
|
'configure_local_memory_target',
|
|
73
73
|
'get_local_memory_target',
|
|
74
74
|
'verify_local_memory_target',
|
|
75
|
-
'capture_memory'
|
|
75
|
+
'capture_memory',
|
|
76
|
+
'invalidate_memory',
|
|
77
|
+
'close_session'
|
|
76
78
|
],
|
|
77
79
|
|
|
78
80
|
// 云端工具(需要网络或云端资源)
|
|
@@ -294,7 +296,10 @@ export class SmartRouter {
|
|
|
294
296
|
}
|
|
295
297
|
|
|
296
298
|
const cloudNames = new Set(cloudTools.map((tool) => tool.name));
|
|
297
|
-
const
|
|
299
|
+
const governedLocalMemoryTools = new Set(['capture_memory', 'invalidate_memory', 'close_session']);
|
|
300
|
+
const eligibleLocalTools = localTools.filter((tool) => (
|
|
301
|
+
!governedLocalMemoryTools.has(tool.name) || cloudNames.has(tool.name)
|
|
302
|
+
));
|
|
298
303
|
const localNames = new Set(eligibleLocalTools.map((tool) => tool.name));
|
|
299
304
|
return [
|
|
300
305
|
...cloudTools.filter((tool) => !localNames.has(tool.name)).map((tool) => ({ ...tool, source: 'cloud' })),
|