cloudflare-mcp-smart-proxy 1.5.8 → 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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-mcp-smart-proxy",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
4
4
  "description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
+ }
@@ -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,7 +72,8 @@ 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;
@@ -82,6 +84,18 @@ export class ConnectorBridge {
82
84
  return resolution;
83
85
  }
84
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
+
85
99
  getBridgeStatus() {
86
100
  return {
87
101
  objectType: 'connector_bridge_status',
@@ -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,