cloudflare-mcp-smart-proxy 1.5.7 → 1.5.9
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/index.js +30 -0
- package/package.json +1 -1
- package/src/client-project-scope.js +63 -0
- package/src/connector-bridge.js +19 -6
- package/src/local-memory-coordinator.js +6 -0
- package/src/router.js +22 -0
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { sanitizeProxyError, SmartRouter } from './src/router.js';
|
|
|
17
17
|
import { LocalToolExecutor } from './src/local-tools.js';
|
|
18
18
|
import { ConnectorBridge } from './src/connector-bridge.js';
|
|
19
19
|
import { LocalMemoryCoordinator } from './src/local-memory-coordinator.js';
|
|
20
|
+
import { resolveClientProjectRoot } from './src/client-project-scope.js';
|
|
20
21
|
import fs from 'fs';
|
|
21
22
|
|
|
22
23
|
// 从环境变量读取配置
|
|
@@ -104,9 +105,37 @@ const server = new Server(
|
|
|
104
105
|
}
|
|
105
106
|
);
|
|
106
107
|
|
|
108
|
+
let projectScopeRefresh = null;
|
|
109
|
+
|
|
110
|
+
async function refreshProjectScope() {
|
|
111
|
+
if (!connectorBridge.isConfigured()) return connectorBridge.getBridgeStatus();
|
|
112
|
+
if (projectScopeRefresh) return projectScopeRefresh;
|
|
113
|
+
projectScopeRefresh = (async () => {
|
|
114
|
+
const clientRoot = await resolveClientProjectRoot(server, connectorBridge.workspaceRoot);
|
|
115
|
+
if (!clientRoot.workspaceRoot) {
|
|
116
|
+
connectorBridge.setProjectRootWarning(clientRoot.warning, clientRoot.roots);
|
|
117
|
+
return connectorBridge.getBridgeStatus();
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
connectorBridge.workspaceRoot !== clientRoot.workspaceRoot
|
|
121
|
+
|| !connectorBridge.state.workspaceResolution
|
|
122
|
+
) {
|
|
123
|
+
await connectorBridge.resolveCurrentWorkspace(clientRoot.workspaceRoot);
|
|
124
|
+
}
|
|
125
|
+
localTools.workspaceRoot = clientRoot.workspaceRoot;
|
|
126
|
+
router.workspaceRoot = clientRoot.workspaceRoot;
|
|
127
|
+
memoryCoordinator.bindWorkspace(clientRoot.workspaceRoot, connectorBridge.workspaceId);
|
|
128
|
+
return connectorBridge.getBridgeStatus();
|
|
129
|
+
})().finally(() => {
|
|
130
|
+
projectScopeRefresh = null;
|
|
131
|
+
});
|
|
132
|
+
return projectScopeRefresh;
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
// 工具列表处理器
|
|
108
136
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
109
137
|
try {
|
|
138
|
+
await refreshProjectScope();
|
|
110
139
|
const tools = await router.getAllTools();
|
|
111
140
|
return { tools };
|
|
112
141
|
} catch (error) {
|
|
@@ -133,6 +162,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
133
162
|
}
|
|
134
163
|
|
|
135
164
|
try {
|
|
165
|
+
await refreshProjectScope();
|
|
136
166
|
const result = await router.executeTool(name, args || {});
|
|
137
167
|
|
|
138
168
|
// 格式化响应
|
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
function normalizeRootUri(uri) {
|
|
5
|
+
if (typeof uri !== 'string' || !uri.trim()) return null;
|
|
6
|
+
try {
|
|
7
|
+
const parsed = new URL(uri);
|
|
8
|
+
if (parsed.protocol !== 'file:') return null;
|
|
9
|
+
return path.resolve(fileURLToPath(parsed));
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function resolveClientProjectRoot(server, fallbackRoot = process.cwd()) {
|
|
16
|
+
if (!server?.getClientCapabilities?.()?.roots || typeof server.listRoots !== 'function') {
|
|
17
|
+
return {
|
|
18
|
+
status: 'fallback',
|
|
19
|
+
workspaceRoot: path.resolve(fallbackRoot),
|
|
20
|
+
roots: []
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = await server.listRoots();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return {
|
|
28
|
+
status: 'warning',
|
|
29
|
+
workspaceRoot: null,
|
|
30
|
+
roots: [],
|
|
31
|
+
warning: {
|
|
32
|
+
code: 'CLIENT_PROJECT_ROOT_LOOKUP_FAILED',
|
|
33
|
+
severity: 'warning',
|
|
34
|
+
scope: 'project',
|
|
35
|
+
message: '当前客户端的项目根查询失败,暂时无法解析项目空间。',
|
|
36
|
+
candidates: [],
|
|
37
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const roots = Array.from(new Set(
|
|
42
|
+
(Array.isArray(result?.roots) ? result.roots : [])
|
|
43
|
+
.map((root) => normalizeRootUri(root?.uri))
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
));
|
|
46
|
+
if (roots.length === 1) {
|
|
47
|
+
return { status: 'resolved', workspaceRoot: roots[0], roots };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
status: 'warning',
|
|
51
|
+
workspaceRoot: null,
|
|
52
|
+
roots,
|
|
53
|
+
warning: {
|
|
54
|
+
code: roots.length ? 'CLIENT_PROJECT_ROOT_AMBIGUOUS' : 'CLIENT_PROJECT_ROOT_UNAVAILABLE',
|
|
55
|
+
severity: 'warning',
|
|
56
|
+
scope: 'project',
|
|
57
|
+
message: roots.length
|
|
58
|
+
? '当前客户端暴露了多个项目根,无法唯一解析项目空间。'
|
|
59
|
+
: '当前客户端没有暴露可用的项目根,无法解析项目空间。',
|
|
60
|
+
candidates: roots
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/connector-bridge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { CloudClient } from './cloud-client.js';
|
|
3
4
|
import { DeviceIdentity } from './device-identity.js';
|
|
4
5
|
import { discoverProjectProbe } from './project-probe-discovery.js';
|
|
@@ -71,19 +72,30 @@ export class ConnectorBridge {
|
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
async resolveCurrentWorkspace() {
|
|
75
|
+
async resolveCurrentWorkspace(workspaceRoot = this.workspaceRoot) {
|
|
76
|
+
this.workspaceRoot = path.resolve(workspaceRoot || process.cwd());
|
|
75
77
|
const projectIdentity = await discoverProjectIdentity(this.workspaceRoot);
|
|
76
78
|
const response = await this.cloudClient.resolveWorkspace({ projectIdentity });
|
|
77
79
|
const resolution = response?.workspaceResolution;
|
|
78
|
-
if (!resolution
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
this.workspaceId = resolution.workspaceId;
|
|
80
|
+
if (!resolution) throw new Error('CloudMCP returned no workspace resolution result');
|
|
81
|
+
this.workspaceId = normalizeString(resolution.workspaceId);
|
|
82
82
|
this.state.projectIdentity = projectIdentity;
|
|
83
83
|
this.state.workspaceResolution = resolution;
|
|
84
84
|
return resolution;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
setProjectRootWarning(warning, roots = []) {
|
|
88
|
+
this.workspaceId = '';
|
|
89
|
+
this.state.workspaceResolution = {
|
|
90
|
+
status: 'warning',
|
|
91
|
+
resolution: 'client_project_root_unresolved',
|
|
92
|
+
workspaceId: null,
|
|
93
|
+
workspaceIds: [],
|
|
94
|
+
warnings: warning ? [warning] : [],
|
|
95
|
+
candidates: roots
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
getBridgeStatus() {
|
|
88
100
|
return {
|
|
89
101
|
objectType: 'connector_bridge_status',
|
|
@@ -107,7 +119,8 @@ export class ConnectorBridge {
|
|
|
107
119
|
return this.getBridgeStatus();
|
|
108
120
|
}
|
|
109
121
|
|
|
110
|
-
await this.resolveCurrentWorkspace();
|
|
122
|
+
const workspaceResolution = await this.resolveCurrentWorkspace();
|
|
123
|
+
if (!workspaceResolution.workspaceId) return this.getBridgeStatus();
|
|
111
124
|
try {
|
|
112
125
|
if (typeof this.cloudClient?.registerDevice === 'function') {
|
|
113
126
|
await this.ensureDeviceRegistration();
|
|
@@ -68,6 +68,12 @@ export class LocalMemoryCoordinator {
|
|
|
68
68
|
this.connectorId = options.connectorId || '';
|
|
69
69
|
this.homeDir = options.homeDir || os.homedir();
|
|
70
70
|
this.cloudToolCaller = options.cloudToolCaller || null;
|
|
71
|
+
this.bindWorkspace(this.workspaceRoot, this.workspaceId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bindWorkspace(workspaceRoot, workspaceId = '') {
|
|
75
|
+
this.workspaceRoot = path.resolve(workspaceRoot || process.cwd());
|
|
76
|
+
this.workspaceId = workspaceId || '';
|
|
71
77
|
const bindingFingerprint = fingerprint([
|
|
72
78
|
this.cloudUrl,
|
|
73
79
|
this.clientProfileId,
|
package/src/router.js
CHANGED
|
@@ -136,11 +136,25 @@ export class SmartRouter {
|
|
|
136
136
|
return 'cloud';
|
|
137
137
|
}
|
|
138
138
|
|
|
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
|
+
|
|
139
145
|
/**
|
|
140
146
|
* 执行工具调用
|
|
141
147
|
*/
|
|
142
148
|
async executeTool(toolName, params) {
|
|
143
149
|
const route = this.routeTool(toolName);
|
|
150
|
+
const workspaceWarnings = this.getWorkspaceWarnings();
|
|
151
|
+
if (route === 'cloud' && workspaceWarnings.length) {
|
|
152
|
+
return {
|
|
153
|
+
objectType: 'connector_workspace_warning',
|
|
154
|
+
executed: false,
|
|
155
|
+
warnings: workspaceWarnings
|
|
156
|
+
};
|
|
157
|
+
}
|
|
144
158
|
|
|
145
159
|
if (route === 'local') {
|
|
146
160
|
return await this.localTools.execute(toolName, params);
|
|
@@ -288,6 +302,14 @@ export class SmartRouter {
|
|
|
288
302
|
* 获取所有工具列表(合并云端和本地)
|
|
289
303
|
*/
|
|
290
304
|
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
|
+
}
|
|
291
313
|
const [cloudTools, localTools] = await Promise.all([
|
|
292
314
|
this.getCloudTools(),
|
|
293
315
|
this.localTools.listTools()
|