cloudflare-mcp-smart-proxy 1.5.6 → 1.5.7

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 CHANGED
@@ -9,6 +9,7 @@ import { reloadCodexMcpServer } from './src/codex-app-server.js';
9
9
  import { installReferenceConnector, printReferenceConnectorConfig } from './src/reference-connectors.js';
10
10
  import { DeviceIdentity } from './src/device-identity.js';
11
11
  import { writeFileAtomically } from './src/reference-connectors.js';
12
+ import { discoverProjectIdentity } from './src/project-identity.js';
12
13
 
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
@@ -202,6 +203,7 @@ async function runActivation(options) {
202
203
  };
203
204
 
204
205
  const createApprovalRequest = async ({ replacesRequestId = '' } = {}) => {
206
+ const projectIdentity = await discoverProjectIdentity(paths.workspaceRoot);
205
207
  const requested = await activationFetch(`${paths.cloudUrl}/connectors/activation-requests`, {
206
208
  method: 'POST',
207
209
  headers: { 'Content-Type': 'application/json' },
@@ -211,6 +213,7 @@ async function runActivation(options) {
211
213
  publicKeyJwk: device.publicKeyJwk,
212
214
  deviceLabel: options['device-label'] || os.hostname(),
213
215
  workspaceLabel: path.basename(paths.workspaceRoot),
216
+ projectIdentity,
214
217
  connectorId: options['connector-id'] || defaultConnectorId(options.ecosystem, paths.workspaceRoot)
215
218
  })
216
219
  });
package/index.js CHANGED
@@ -38,11 +38,14 @@ function readManagedCredential() {
38
38
  const MANAGED_CREDENTIAL = readManagedCredential();
39
39
  const CLOUD_URL = MANAGED_CREDENTIAL?.cloudUrl || process.env.CLOUDFLARE_MCP_URL || process.env.MCP_URL;
40
40
  const CLOUD_API_KEY = MANAGED_CREDENTIAL?.apiKey || process.env.CLOUDFLARE_MCP_API_KEY || process.env.MCP_API_KEY;
41
- const WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || process.cwd();
41
+ const FIXED_PROJECT_CONTEXT = process.env.CLOUDMCP_FIXED_PROJECT_CONTEXT === 'true';
42
+ const WORKSPACE_ROOT = FIXED_PROJECT_CONTEXT ? (process.env.WORKSPACE_ROOT || process.cwd()) : process.cwd();
42
43
  const CLIENT_PROFILE_ID = MANAGED_CREDENTIAL?.clientProfileId || process.env.CLOUDMCP_CLIENT_PROFILE_ID || process.env.CLIENT_PROFILE_ID || '';
43
44
  const CONNECTOR_ID = MANAGED_CREDENTIAL?.connectorId || process.env.CLOUDMCP_CONNECTOR_ID || process.env.CONNECTOR_ID || '';
44
45
  const CONNECTOR_TYPE = MANAGED_CREDENTIAL?.connectorType || process.env.CLOUDMCP_CONNECTOR_TYPE || process.env.CONNECTOR_TYPE || 'smart_proxy';
45
- const WORKSPACE_ID = MANAGED_CREDENTIAL?.workspaceId || process.env.CLOUDMCP_WORKSPACE_ID || process.env.WORKSPACE_ID || '';
46
+ const WORKSPACE_ID = FIXED_PROJECT_CONTEXT
47
+ ? (process.env.CLOUDMCP_WORKSPACE_ID || process.env.WORKSPACE_ID || '')
48
+ : '';
46
49
  const DEVICE_IDENTITY_PATH = process.env.CLOUDMCP_DEVICE_IDENTITY_PATH || '';
47
50
  const AUTO_SYNC_PROFILE = (process.env.CLOUDMCP_AUTO_SYNC_PROFILE || 'true') !== 'false';
48
51
  const AUTO_APPLY_BRAIN = (process.env.CLOUDMCP_AUTO_APPLY_BRAIN || 'true') !== 'false';
@@ -77,7 +80,14 @@ const memoryCoordinator = new LocalMemoryCoordinator({
77
80
  connectorId: CONNECTOR_ID
78
81
  });
79
82
  const localTools = new LocalToolExecutor(WORKSPACE_ROOT, connectorBridge, memoryCoordinator);
80
- const router = new SmartRouter(CLOUD_URL, CLOUD_API_KEY, localTools, WORKSPACE_ROOT, connectorBridge.deviceIdentity);
83
+ const router = new SmartRouter(
84
+ CLOUD_URL,
85
+ CLOUD_API_KEY,
86
+ localTools,
87
+ WORKSPACE_ROOT,
88
+ connectorBridge.deviceIdentity,
89
+ () => connectorBridge.getScopeHeaders()
90
+ );
81
91
  localTools.setCloudToolCaller((tool, params) => router.callCloudTool(tool, params));
82
92
 
83
93
  // 创建 MCP 服务器
@@ -163,7 +173,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
163
173
  method: 'POST',
164
174
  headers: {
165
175
  'Authorization': `Bearer ${CLOUD_API_KEY}`,
166
- 'Content-Type': 'application/json'
176
+ 'Content-Type': 'application/json',
177
+ ...connectorBridge.getScopeHeaders()
167
178
  },
168
179
  body: JSON.stringify({
169
180
  jsonrpc: '2.0',
@@ -199,7 +210,8 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
199
210
  method: 'POST',
200
211
  headers: {
201
212
  'Authorization': `Bearer ${CLOUD_API_KEY}`,
202
- 'Content-Type': 'application/json'
213
+ 'Content-Type': 'application/json',
214
+ ...connectorBridge.getScopeHeaders()
203
215
  },
204
216
  body: JSON.stringify({
205
217
  jsonrpc: '2.0',
@@ -240,18 +252,22 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
240
252
  // 启动服务器
241
253
  async function main() {
242
254
  try {
243
- const transport = new StdioServerTransport();
244
- await server.connect(transport);
245
- console.error('Cloudflare MCP Smart Proxy started');
246
- console.error(`Workspace root: ${WORKSPACE_ROOT}`);
247
- console.error(`Cloud URL: ${CLOUD_URL}`);
255
+ let bridgeStatus = null;
248
256
  if (connectorBridge.isConfigured()) {
249
- const bridgeStatus = await connectorBridge.initialize({
257
+ bridgeStatus = await connectorBridge.initialize({
250
258
  autoApplyBrain: AUTO_APPLY_BRAIN,
251
259
  autoSyncProfile: AUTO_SYNC_PROFILE,
252
260
  autoReportProjectProbe: AUTO_REPORT_PROJECT_PROBE,
253
261
  autoGenerateContextPack: AUTO_GENERATE_CONTEXT_PACK
254
262
  });
263
+ memoryCoordinator.workspaceId = connectorBridge.workspaceId;
264
+ }
265
+ const transport = new StdioServerTransport();
266
+ await server.connect(transport);
267
+ console.error('Cloudflare MCP Smart Proxy started');
268
+ console.error(`Workspace root: ${WORKSPACE_ROOT}`);
269
+ console.error(`Cloud URL: ${CLOUD_URL}`);
270
+ if (bridgeStatus) {
255
271
  console.error(`Connector bridge profile: ${bridgeStatus.clientProfileId}`);
256
272
  console.error(`Connector bridge workspace: ${bridgeStatus.workspaceId}`);
257
273
  const applyResult = bridgeStatus.state?.lastBrainApplyResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-mcp-smart-proxy",
3
- "version": "1.5.6",
3
+ "version": "1.5.7",
4
4
  "description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,10 +1,11 @@
1
1
  const CONNECTOR_CONTRACT_VERSION = 'a2.v1';
2
2
 
3
3
  export class CloudClient {
4
- constructor({ cloudUrl, cloudApiKey, deviceIdentity = null }) {
4
+ constructor({ cloudUrl, cloudApiKey, deviceIdentity = null, getScopeHeaders = null }) {
5
5
  this.cloudUrl = String(cloudUrl || '').replace(/\/$/, '');
6
6
  this.cloudApiKey = cloudApiKey || '';
7
7
  this.deviceIdentity = deviceIdentity;
8
+ this.getScopeHeaders = typeof getScopeHeaders === 'function' ? getScopeHeaders : () => ({});
8
9
  if (!this.cloudUrl) {
9
10
  throw new Error('cloudUrl is required');
10
11
  }
@@ -42,6 +43,7 @@ export class CloudClient {
42
43
  'Authorization': `Bearer ${this.cloudApiKey}`,
43
44
  'Content-Type': 'application/json',
44
45
  'X-CloudMCP-Connector-Contract-Version': CONNECTOR_CONTRACT_VERSION,
46
+ ...this.getScopeHeaders(),
45
47
  ...signedHeaders,
46
48
  ...(idempotencyKey ? { 'X-Idempotency-Key': idempotencyKey } : {})
47
49
  },
@@ -73,6 +75,13 @@ export class CloudClient {
73
75
  });
74
76
  }
75
77
 
78
+ async resolveWorkspace({ projectIdentity }) {
79
+ return this.request('/connectors/workspace-resolution', {
80
+ method: 'POST',
81
+ body: { projectIdentity }
82
+ });
83
+ }
84
+
76
85
  async listConnectorStatusReports({ clientProfileId, workspaceId, connectorId, limit = 5 }) {
77
86
  return this.request('/connectors/status-reports', {
78
87
  query: { clientProfileId, workspaceId, connectorId, limit }
@@ -1,9 +1,9 @@
1
1
  import os from 'os';
2
- import path from 'path';
3
2
  import { CloudClient } from './cloud-client.js';
4
3
  import { DeviceIdentity } from './device-identity.js';
5
4
  import { discoverProjectProbe } from './project-probe-discovery.js';
6
5
  import { detectIde, applyBrainSnapshot } from './ide-configurator.js';
6
+ import { discoverProjectIdentity } from './project-identity.js';
7
7
 
8
8
  function normalizeString(value, fallback = '') {
9
9
  const normalized = typeof value === 'string' ? value.trim() : '';
@@ -14,10 +14,6 @@ function defaultConnectorId() {
14
14
  return `connector.${os.hostname().replace(/[^a-zA-Z0-9_.-]/g, '_')}.local`;
15
15
  }
16
16
 
17
- function defaultWorkspaceId(workspaceRoot) {
18
- return `workspace.${path.basename(workspaceRoot || process.cwd()).replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
19
- }
20
-
21
17
  function buildIdempotencyKey(...parts) {
22
18
  return parts
23
19
  .map((entry) => normalizeString(entry))
@@ -42,12 +38,13 @@ export class ConnectorBridge {
42
38
  this.clientProfileId = normalizeString(clientProfileId);
43
39
  this.connectorId = normalizeString(connectorId, defaultConnectorId());
44
40
  this.connectorType = normalizeString(connectorType, 'smart_proxy');
45
- this.workspaceId = normalizeString(workspaceId, defaultWorkspaceId(this.workspaceRoot));
41
+ this.workspaceId = normalizeString(workspaceId);
46
42
  this.deviceIdentity = deviceIdentity || new DeviceIdentity({ identityPath: deviceIdentityPath });
47
43
  this.cloudClient = cloudClient || new CloudClient({
48
44
  cloudUrl,
49
45
  cloudApiKey,
50
- deviceIdentity: this.deviceIdentity
46
+ deviceIdentity: this.deviceIdentity,
47
+ getScopeHeaders: () => this.getScopeHeaders()
51
48
  });
52
49
  this.state = {
53
50
  initializedAt: Date.now(),
@@ -66,6 +63,27 @@ export class ConnectorBridge {
66
63
  return Boolean(this.clientProfileId);
67
64
  }
68
65
 
66
+ getScopeHeaders() {
67
+ return {
68
+ ...(this.workspaceId ? { 'X-CloudMCP-Workspace-ID': this.workspaceId } : {}),
69
+ ...(this.clientProfileId ? { 'X-CloudMCP-Client-Profile-ID': this.clientProfileId } : {}),
70
+ ...(this.connectorId ? { 'X-CloudMCP-Connector-ID': this.connectorId } : {})
71
+ };
72
+ }
73
+
74
+ async resolveCurrentWorkspace() {
75
+ const projectIdentity = await discoverProjectIdentity(this.workspaceRoot);
76
+ const response = await this.cloudClient.resolveWorkspace({ projectIdentity });
77
+ const resolution = response?.workspaceResolution;
78
+ if (!resolution?.workspaceId) {
79
+ throw new Error('CloudMCP did not resolve a workspace for the current project');
80
+ }
81
+ this.workspaceId = resolution.workspaceId;
82
+ this.state.projectIdentity = projectIdentity;
83
+ this.state.workspaceResolution = resolution;
84
+ return resolution;
85
+ }
86
+
69
87
  getBridgeStatus() {
70
88
  return {
71
89
  objectType: 'connector_bridge_status',
@@ -89,6 +107,7 @@ export class ConnectorBridge {
89
107
  return this.getBridgeStatus();
90
108
  }
91
109
 
110
+ await this.resolveCurrentWorkspace();
92
111
  try {
93
112
  if (typeof this.cloudClient?.registerDevice === 'function') {
94
113
  await this.ensureDeviceRegistration();
@@ -0,0 +1,75 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ function normalizeString(value, fallback = '') {
5
+ const normalized = typeof value === 'string' ? value.trim() : '';
6
+ return normalized || fallback;
7
+ }
8
+
9
+ function normalizeAlias(value) {
10
+ return normalizeString(value)
11
+ .toLowerCase()
12
+ .replace(/\.git$/i, '')
13
+ .replace(/\/+$/, '');
14
+ }
15
+
16
+ function normalizeGitRemote(value) {
17
+ const remote = normalizeString(value);
18
+ if (!remote) return '';
19
+ const scpMatch = remote.match(/^(?:[^@]+@)?([^:]+):(.+)$/);
20
+ if (scpMatch && !remote.includes('://')) {
21
+ return normalizeAlias(`${scpMatch[1]}/${scpMatch[2]}`);
22
+ }
23
+ try {
24
+ const parsed = new URL(remote);
25
+ return normalizeAlias(`${parsed.hostname}${parsed.pathname}`);
26
+ } catch {
27
+ return normalizeAlias(remote);
28
+ }
29
+ }
30
+
31
+ async function readText(filePath) {
32
+ try {
33
+ return await fs.readFile(filePath, 'utf8');
34
+ } catch {
35
+ return '';
36
+ }
37
+ }
38
+
39
+ async function readPackageName(repoRoot) {
40
+ try {
41
+ const payload = JSON.parse(await fs.readFile(path.join(repoRoot, 'package.json'), 'utf8'));
42
+ return normalizeAlias(payload?.name);
43
+ } catch {
44
+ return '';
45
+ }
46
+ }
47
+
48
+ async function readGitRemote(repoRoot) {
49
+ const gitConfig = await readText(path.join(repoRoot, '.git', 'config'));
50
+ const originSection = gitConfig.match(/\[remote\s+"origin"\]([\s\S]*?)(?=\n\[|$)/i)?.[1] || '';
51
+ const originUrl = originSection.match(/^\s*url\s*=\s*(.+)$/mi)?.[1] || '';
52
+ return normalizeGitRemote(originUrl);
53
+ }
54
+
55
+ export async function discoverProjectIdentity(workspaceRoot = process.cwd()) {
56
+ const repoRoot = path.resolve(workspaceRoot || process.cwd());
57
+ const repoName = path.basename(repoRoot);
58
+ const [gitRemote, packageName] = await Promise.all([
59
+ readGitRemote(repoRoot),
60
+ readPackageName(repoRoot)
61
+ ]);
62
+ const aliases = Array.from(new Set([
63
+ gitRemote ? `git:${gitRemote}` : '',
64
+ packageName ? `package:${packageName}` : '',
65
+ `repo:${normalizeAlias(repoName)}`
66
+ ].filter(Boolean)));
67
+
68
+ return {
69
+ canonicalId: aliases[0],
70
+ aliases,
71
+ repoName,
72
+ gitRemote: gitRemote || null,
73
+ packageName: packageName || null
74
+ };
75
+ }
@@ -100,7 +100,8 @@ function toManagedEnv({
100
100
  connectorType,
101
101
  workspaceId,
102
102
  deviceIdentityPath,
103
- codexConfigPath
103
+ codexConfigPath,
104
+ fixedProjectContext = false
104
105
  }) {
105
106
  const env = {
106
107
  CLOUDFLARE_MCP_URL: normalizeEnvValue(cloudUrl),
@@ -112,7 +113,8 @@ function toManagedEnv({
112
113
  CLOUDMCP_CONNECTOR_TYPE: normalizeEnvValue(connectorType),
113
114
  CLOUDMCP_WORKSPACE_ID: normalizeEnvValue(workspaceId),
114
115
  CLOUDMCP_DEVICE_IDENTITY_PATH: normalizeEnvValue(deviceIdentityPath),
115
- CODEX_SHARED_CONFIG_PATH: normalizeEnvValue(codexConfigPath)
116
+ CODEX_SHARED_CONFIG_PATH: normalizeEnvValue(codexConfigPath),
117
+ CLOUDMCP_FIXED_PROJECT_CONTEXT: fixedProjectContext ? 'true' : ''
116
118
  };
117
119
 
118
120
  return Object.fromEntries(
@@ -156,6 +158,7 @@ export function buildReferenceConnectorServer({
156
158
  workspaceId = '',
157
159
  deviceIdentityPath = '',
158
160
  codexConfigPath = '',
161
+ fixedProjectContext = true,
159
162
  packageRoot = null,
160
163
  runtime = 'npm'
161
164
  }) {
@@ -174,13 +177,14 @@ export function buildReferenceConnectorServer({
174
177
  cloudUrl,
175
178
  cloudApiKey,
176
179
  credentialPath,
177
- workspaceRoot: resolvedWorkspaceRoot,
180
+ workspaceRoot: fixedProjectContext ? resolvedWorkspaceRoot : '',
178
181
  clientProfileId,
179
182
  connectorId: resolvedConnectorId,
180
183
  connectorType: resolvedConnectorType,
181
- workspaceId: resolvedWorkspaceId,
184
+ workspaceId: fixedProjectContext ? resolvedWorkspaceId : '',
182
185
  deviceIdentityPath,
183
- codexConfigPath: normalizedEcosystem === 'codex' ? codexConfigPath : ''
186
+ codexConfigPath: normalizedEcosystem === 'codex' ? codexConfigPath : '',
187
+ fixedProjectContext
184
188
  }),
185
189
  enabled: true,
186
190
  startupTimeoutSec: normalizedEcosystem === 'codex' ? DEFAULT_CODEX_STARTUP_TIMEOUT_SEC : null,
@@ -358,6 +362,7 @@ export function installReferenceConnector({
358
362
  workspaceId,
359
363
  deviceIdentityPath,
360
364
  codexConfigPath: target.ecosystem === 'codex' ? target.targetFile : '',
365
+ fixedProjectContext: target.scope === 'project',
361
366
  packageRoot,
362
367
  runtime
363
368
  });
package/src/router.js CHANGED
@@ -38,12 +38,13 @@ export function sanitizeProxyError(error, params = {}) {
38
38
  }
39
39
 
40
40
  export class SmartRouter {
41
- constructor(cloudUrl, cloudApiKey, localTools, workspaceRoot = null, deviceIdentity = null) {
41
+ constructor(cloudUrl, cloudApiKey, localTools, workspaceRoot = null, deviceIdentity = null, getScopeHeaders = null) {
42
42
  this.cloudUrl = cloudUrl.replace(/\/$/, ''); // 移除尾部斜杠
43
43
  this.cloudApiKey = cloudApiKey;
44
44
  this.localTools = localTools;
45
45
  this.workspaceRoot = workspaceRoot || process.cwd();
46
46
  this.deviceIdentity = deviceIdentity;
47
+ this.getScopeHeaders = typeof getScopeHeaders === 'function' ? getScopeHeaders : () => ({});
47
48
 
48
49
  // 工具路由规则
49
50
  this.routingRules = {
@@ -215,6 +216,7 @@ export class SmartRouter {
215
216
  headers: {
216
217
  'Authorization': `Bearer ${this.cloudApiKey}`,
217
218
  'Content-Type': 'application/json',
219
+ ...this.getScopeHeaders(),
218
220
  ...signedHeaders
219
221
  },
220
222
  body
@@ -325,6 +327,7 @@ export class SmartRouter {
325
327
  headers: {
326
328
  'Authorization': `Bearer ${this.cloudApiKey}`,
327
329
  'Content-Type': 'application/json',
330
+ ...this.getScopeHeaders(),
328
331
  ...signedHeaders
329
332
  },
330
333
  body