cloudflare-mcp-smart-proxy 1.2.0 → 1.3.0
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 +74 -142
- package/connector-cli.js +144 -0
- package/index.js +37 -3
- package/package.json +7 -3
- package/src/cloud-client.js +135 -0
- package/src/connector-bridge.js +284 -0
- package/src/device-identity.js +87 -0
- package/src/ide-configurator.js +316 -0
- package/src/local-tools.js +84 -2
- package/src/project-probe-discovery.js +137 -0
- package/src/reference-connectors.js +315 -0
- package/src/router.js +35 -20
- package/package-personal.json +0 -28
- package/publish-now.sh +0 -230
- package/publish-with-2fa.sh +0 -134
- package/publish.sh +0 -90
- package/version-bump.sh +0 -128
package/src/local-tools.js
CHANGED
|
@@ -10,8 +10,9 @@ import { SymbolTools } from './tools/symbol-tools.js';
|
|
|
10
10
|
import { Diagnostics } from './tools/diagnostics.js';
|
|
11
11
|
|
|
12
12
|
export class LocalToolExecutor {
|
|
13
|
-
constructor(workspaceRoot) {
|
|
13
|
+
constructor(workspaceRoot, connectorBridge = null) {
|
|
14
14
|
this.workspaceRoot = workspaceRoot;
|
|
15
|
+
this.connectorBridge = connectorBridge;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -55,6 +56,26 @@ export class LocalToolExecutor {
|
|
|
55
56
|
|
|
56
57
|
case 'get_diagnostics':
|
|
57
58
|
return await Diagnostics.getDiagnostics(params, this.workspaceRoot);
|
|
59
|
+
|
|
60
|
+
case 'connector_bridge_status':
|
|
61
|
+
if (!this.connectorBridge) throw new Error('Connector bridge is not configured');
|
|
62
|
+
return this.connectorBridge.getBridgeStatus();
|
|
63
|
+
|
|
64
|
+
case 'connector_sync_profile':
|
|
65
|
+
if (!this.connectorBridge) throw new Error('Connector bridge is not configured');
|
|
66
|
+
return await this.connectorBridge.syncProfile();
|
|
67
|
+
|
|
68
|
+
case 'connector_report_status':
|
|
69
|
+
if (!this.connectorBridge) throw new Error('Connector bridge is not configured');
|
|
70
|
+
return await this.connectorBridge.reportStatus(params || {});
|
|
71
|
+
|
|
72
|
+
case 'connector_discover_project_probe':
|
|
73
|
+
if (!this.connectorBridge) throw new Error('Connector bridge is not configured');
|
|
74
|
+
return await this.connectorBridge.discoverProjectProbe();
|
|
75
|
+
|
|
76
|
+
case 'connector_report_project_probe':
|
|
77
|
+
if (!this.connectorBridge) throw new Error('Connector bridge is not configured');
|
|
78
|
+
return await this.connectorBridge.reportProjectProbe(params || {});
|
|
58
79
|
|
|
59
80
|
default:
|
|
60
81
|
throw new Error(`Unknown local tool: ${toolName}`);
|
|
@@ -314,8 +335,69 @@ export class LocalToolExecutor {
|
|
|
314
335
|
},
|
|
315
336
|
required: ['file_path']
|
|
316
337
|
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'connector_bridge_status',
|
|
341
|
+
description: 'Get current connector bridge state, sync metadata, and workspace binding status',
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'connector_sync_profile',
|
|
349
|
+
description: 'Pull the latest install plan and recent status reports for the configured client profile',
|
|
350
|
+
inputSchema: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
properties: {}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'connector_report_status',
|
|
357
|
+
description: 'Report local connector apply status back to CloudMCP',
|
|
358
|
+
inputSchema: {
|
|
359
|
+
type: 'object',
|
|
360
|
+
properties: {
|
|
361
|
+
status: {
|
|
362
|
+
type: 'string',
|
|
363
|
+
description: 'Overall status, for example succeeded, degraded, or failed'
|
|
364
|
+
},
|
|
365
|
+
summary: {
|
|
366
|
+
type: 'object',
|
|
367
|
+
description: 'High level summary of local apply results'
|
|
368
|
+
},
|
|
369
|
+
operationResults: {
|
|
370
|
+
type: 'array',
|
|
371
|
+
description: 'Per-operation execution results'
|
|
372
|
+
},
|
|
373
|
+
issues: {
|
|
374
|
+
type: 'array',
|
|
375
|
+
description: 'Reported install or runtime issues'
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'connector_discover_project_probe',
|
|
382
|
+
description: 'Discover the local workspace skeleton and build a project_probe candidate payload',
|
|
383
|
+
inputSchema: {
|
|
384
|
+
type: 'object',
|
|
385
|
+
properties: {}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'connector_report_project_probe',
|
|
390
|
+
description: 'Report the local workspace project probe to CloudMCP and optionally auto-generate a draft context pack',
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: 'object',
|
|
393
|
+
properties: {
|
|
394
|
+
autoGenerateContextPack: {
|
|
395
|
+
type: 'boolean',
|
|
396
|
+
description: 'Whether CloudMCP should auto-generate a draft context pack from this probe'
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
317
400
|
}
|
|
318
401
|
];
|
|
319
402
|
}
|
|
320
403
|
}
|
|
321
|
-
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
async function pathExists(target) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(target);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function safeReadDir(target) {
|
|
14
|
+
try {
|
|
15
|
+
return await fs.readdir(target, { withFileTypes: true });
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function inferLanguages(fileNames = []) {
|
|
22
|
+
const set = new Set();
|
|
23
|
+
if (fileNames.some((name) => name.endsWith('.js') || name.endsWith('.mjs') || name.endsWith('.cjs'))) set.add('javascript');
|
|
24
|
+
if (fileNames.some((name) => name.endsWith('.ts') || name.endsWith('.tsx'))) set.add('typescript');
|
|
25
|
+
if (fileNames.some((name) => name.endsWith('.py'))) set.add('python');
|
|
26
|
+
if (fileNames.some((name) => name.endsWith('.go'))) set.add('go');
|
|
27
|
+
if (fileNames.some((name) => name.endsWith('.rs'))) set.add('rust');
|
|
28
|
+
if (fileNames.some((name) => name.endsWith('.java'))) set.add('java');
|
|
29
|
+
if (fileNames.some((name) => name.endsWith('.html'))) set.add('html');
|
|
30
|
+
if (fileNames.some((name) => name.endsWith('.css'))) set.add('css');
|
|
31
|
+
return Array.from(set);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function inferFrameworkHints(fileNames = [], dirs = []) {
|
|
35
|
+
const set = new Set();
|
|
36
|
+
if (fileNames.includes('wrangler.toml')) set.add('cloudflare_workers');
|
|
37
|
+
if (fileNames.includes('package.json')) set.add('node_workspace');
|
|
38
|
+
if (fileNames.includes('pnpm-workspace.yaml') || dirs.includes('packages') || dirs.includes('apps')) set.add('monorepo');
|
|
39
|
+
if (fileNames.includes('next.config.js') || fileNames.includes('next.config.mjs')) set.add('nextjs');
|
|
40
|
+
if (fileNames.includes('vite.config.js') || fileNames.includes('vite.config.ts')) set.add('vite');
|
|
41
|
+
if (fileNames.includes('go.mod')) set.add('go_module');
|
|
42
|
+
if (fileNames.includes('pyproject.toml')) set.add('python_project');
|
|
43
|
+
return Array.from(set);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function inferPackageManager(fileNames = []) {
|
|
47
|
+
if (fileNames.includes('pnpm-lock.yaml')) return 'pnpm';
|
|
48
|
+
if (fileNames.includes('yarn.lock')) return 'yarn';
|
|
49
|
+
if (fileNames.includes('package-lock.json')) return 'npm';
|
|
50
|
+
if (fileNames.includes('bun.lockb') || fileNames.includes('bun.lock')) return 'bun';
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildWorkspaceLayout(dirs = []) {
|
|
55
|
+
const preferred = ['apps', 'packages', 'src', 'tests', 'docs', 'scripts'];
|
|
56
|
+
const found = preferred.filter((name) => dirs.includes(name));
|
|
57
|
+
return found.join(' + ');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function discoverProjectProbe({
|
|
61
|
+
workspaceRoot,
|
|
62
|
+
workspaceId,
|
|
63
|
+
clientProfileId = null,
|
|
64
|
+
connectorId = null,
|
|
65
|
+
connectorType = null,
|
|
66
|
+
generatedBy = 'connector_scan'
|
|
67
|
+
}) {
|
|
68
|
+
const repoRoot = path.resolve(workspaceRoot || process.cwd());
|
|
69
|
+
const repoName = path.basename(repoRoot);
|
|
70
|
+
const topLevel = await safeReadDir(repoRoot);
|
|
71
|
+
const dirNames = topLevel.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
72
|
+
const fileNames = topLevel.filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
73
|
+
|
|
74
|
+
const keyFiles = [
|
|
75
|
+
'wrangler.toml',
|
|
76
|
+
'package.json',
|
|
77
|
+
'pnpm-workspace.yaml',
|
|
78
|
+
'README.md',
|
|
79
|
+
'src/worker.js',
|
|
80
|
+
'src/admin-page.html',
|
|
81
|
+
'docs/CLOUDMCP_CURRENT_DESIGN_FRAMEWORK_AND_DATAFLOW_2026-05-07.md'
|
|
82
|
+
];
|
|
83
|
+
const docCandidates = [
|
|
84
|
+
'README.md',
|
|
85
|
+
'docs/CLOUDMCP_CURRENT_DESIGN_FRAMEWORK_AND_DATAFLOW_2026-05-07.md',
|
|
86
|
+
'docs/CLOUDMCP_INTERNAL_CAPABILITY_BRIDGE_STANDARD_2026-05-06.md',
|
|
87
|
+
'docs/CLOUDMCP_EXTERNAL_TOOL_SURFACE_STANDARD_2026-05-06.md'
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const resolvedKeyFiles = [];
|
|
91
|
+
for (const relativePath of keyFiles) {
|
|
92
|
+
if (await pathExists(path.join(repoRoot, relativePath))) {
|
|
93
|
+
resolvedKeyFiles.push(relativePath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const resolvedDocCandidates = [];
|
|
98
|
+
for (const relativePath of docCandidates) {
|
|
99
|
+
if (await pathExists(path.join(repoRoot, relativePath))) {
|
|
100
|
+
resolvedDocCandidates.push(relativePath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const languages = inferLanguages(fileNames.concat(resolvedKeyFiles));
|
|
105
|
+
const frameworkHints = inferFrameworkHints(fileNames, dirNames);
|
|
106
|
+
const packageManager = inferPackageManager(fileNames);
|
|
107
|
+
const workspaceLayout = buildWorkspaceLayout(dirNames);
|
|
108
|
+
const commandHints = [];
|
|
109
|
+
if (packageManager) {
|
|
110
|
+
commandHints.push(`${packageManager} test`);
|
|
111
|
+
commandHints.push(`${packageManager} run gen-admin`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
id: `project_probe.${repoName}.${(workspaceId || 'default').replace(/[^a-zA-Z0-9_.-]/g, '_')}`,
|
|
116
|
+
clientProfileId,
|
|
117
|
+
connectorId,
|
|
118
|
+
connectorType,
|
|
119
|
+
projectId: repoName.toLowerCase(),
|
|
120
|
+
workspaceId: workspaceId || null,
|
|
121
|
+
repoName,
|
|
122
|
+
repoRoot,
|
|
123
|
+
gitRemote: null,
|
|
124
|
+
defaultBranch: 'main',
|
|
125
|
+
languages,
|
|
126
|
+
frameworkHints,
|
|
127
|
+
packageManager: packageManager || null,
|
|
128
|
+
workspaceLayout: workspaceLayout || null,
|
|
129
|
+
keyFiles: resolvedKeyFiles,
|
|
130
|
+
docCandidates: resolvedDocCandidates,
|
|
131
|
+
commandHints,
|
|
132
|
+
summary: `${repoName} 项目骨架自动探测结果`,
|
|
133
|
+
notes: '由本地 connector bridge 自动生成,后续应由 CloudMCP 生成候选上下文包并进入确认流程。',
|
|
134
|
+
generatedBy,
|
|
135
|
+
status: 'ready_for_context'
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SERVER_NAME = 'cloudmcp';
|
|
6
|
+
const DEFAULT_PACKAGE_NAME = 'cloudflare-mcp-smart-proxy';
|
|
7
|
+
const DEFAULT_PACKAGE_BIN = 'cloudflare-mcp-proxy';
|
|
8
|
+
const MANAGED_BLOCK_PREFIX = '# BEGIN CLOUDMCP MANAGED MCP SERVER';
|
|
9
|
+
const MANAGED_BLOCK_SUFFIX = '# END CLOUDMCP MANAGED MCP SERVER';
|
|
10
|
+
|
|
11
|
+
function normalizeString(value, fallback = '') {
|
|
12
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
13
|
+
return normalized || fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeEnvValue(value) {
|
|
17
|
+
if (value == null) return '';
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sanitizeServerName(value) {
|
|
22
|
+
const candidate = normalizeString(value, DEFAULT_SERVER_NAME)
|
|
23
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
24
|
+
return candidate || DEFAULT_SERVER_NAME;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveHomeDir(homeDir = null) {
|
|
28
|
+
return homeDir || os.homedir();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildWorkspaceId(workspaceRoot) {
|
|
32
|
+
return `workspace.${path.basename(workspaceRoot || process.cwd()).replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildConnectorId(ecosystem, workspaceRoot) {
|
|
36
|
+
return `connector.${sanitizeServerName(ecosystem)}.${path.basename(workspaceRoot || process.cwd()).replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureDir(targetPath) {
|
|
40
|
+
const dirPath = path.dirname(targetPath);
|
|
41
|
+
if (!fs.existsSync(dirPath)) {
|
|
42
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readJson(filePath, fallback) {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
49
|
+
} catch {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function escapeTomlString(value) {
|
|
55
|
+
return String(value)
|
|
56
|
+
.replace(/\\/g, '\\\\')
|
|
57
|
+
.replace(/"/g, '\\"');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatTomlArray(values) {
|
|
61
|
+
return `[${values.map((value) => `"${escapeTomlString(value)}"`).join(', ')}]`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toManagedEnv({
|
|
65
|
+
cloudUrl,
|
|
66
|
+
cloudApiKey,
|
|
67
|
+
workspaceRoot,
|
|
68
|
+
clientProfileId,
|
|
69
|
+
connectorId,
|
|
70
|
+
connectorType,
|
|
71
|
+
workspaceId
|
|
72
|
+
}) {
|
|
73
|
+
const env = {
|
|
74
|
+
CLOUDFLARE_MCP_URL: normalizeEnvValue(cloudUrl),
|
|
75
|
+
CLOUDFLARE_MCP_API_KEY: normalizeEnvValue(cloudApiKey),
|
|
76
|
+
WORKSPACE_ROOT: normalizeEnvValue(workspaceRoot),
|
|
77
|
+
CLOUDMCP_CLIENT_PROFILE_ID: normalizeEnvValue(clientProfileId),
|
|
78
|
+
CLOUDMCP_CONNECTOR_ID: normalizeEnvValue(connectorId),
|
|
79
|
+
CLOUDMCP_CONNECTOR_TYPE: normalizeEnvValue(connectorType),
|
|
80
|
+
CLOUDMCP_WORKSPACE_ID: normalizeEnvValue(workspaceId)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return Object.fromEntries(
|
|
84
|
+
Object.entries(env).filter(([, value]) => value !== '')
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveReferenceConnectorCommand({
|
|
89
|
+
packageRoot = null,
|
|
90
|
+
runtime = 'npm'
|
|
91
|
+
} = {}) {
|
|
92
|
+
if (runtime === 'npm') {
|
|
93
|
+
if (process.platform === 'win32') {
|
|
94
|
+
return {
|
|
95
|
+
command: 'cmd',
|
|
96
|
+
args: ['/c', 'npx', '-y', '-p', DEFAULT_PACKAGE_NAME, DEFAULT_PACKAGE_BIN]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
command: 'npx',
|
|
101
|
+
args: ['-y', '-p', DEFAULT_PACKAGE_NAME, DEFAULT_PACKAGE_BIN]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolvedPackageRoot = path.resolve(packageRoot || path.join(process.cwd(), 'local-proxy'));
|
|
106
|
+
return {
|
|
107
|
+
command: process.execPath,
|
|
108
|
+
args: [path.join(resolvedPackageRoot, 'index.js')]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function buildReferenceConnectorServer({
|
|
113
|
+
ecosystem,
|
|
114
|
+
cloudUrl,
|
|
115
|
+
cloudApiKey,
|
|
116
|
+
workspaceRoot = process.cwd(),
|
|
117
|
+
clientProfileId,
|
|
118
|
+
connectorId = '',
|
|
119
|
+
connectorType = '',
|
|
120
|
+
workspaceId = '',
|
|
121
|
+
packageRoot = null,
|
|
122
|
+
runtime = 'npm'
|
|
123
|
+
}) {
|
|
124
|
+
const normalizedEcosystem = normalizeString(ecosystem).toLowerCase();
|
|
125
|
+
const resolvedWorkspaceRoot = path.resolve(workspaceRoot || process.cwd());
|
|
126
|
+
const resolvedConnectorType = normalizeString(connectorType, normalizedEcosystem || 'reference_connector');
|
|
127
|
+
const resolvedWorkspaceId = normalizeString(workspaceId, buildWorkspaceId(resolvedWorkspaceRoot));
|
|
128
|
+
const resolvedConnectorId = normalizeString(connectorId, buildConnectorId(normalizedEcosystem, resolvedWorkspaceRoot));
|
|
129
|
+
const commandDescriptor = resolveReferenceConnectorCommand({ packageRoot, runtime });
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
type: 'stdio',
|
|
133
|
+
command: commandDescriptor.command,
|
|
134
|
+
args: commandDescriptor.args,
|
|
135
|
+
env: toManagedEnv({
|
|
136
|
+
cloudUrl,
|
|
137
|
+
cloudApiKey,
|
|
138
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
139
|
+
clientProfileId,
|
|
140
|
+
connectorId: resolvedConnectorId,
|
|
141
|
+
connectorType: resolvedConnectorType,
|
|
142
|
+
workspaceId: resolvedWorkspaceId
|
|
143
|
+
}),
|
|
144
|
+
enabled: true,
|
|
145
|
+
connectorId: resolvedConnectorId,
|
|
146
|
+
connectorType: resolvedConnectorType,
|
|
147
|
+
workspaceId: resolvedWorkspaceId
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getReferenceConnectorTarget({
|
|
152
|
+
ecosystem,
|
|
153
|
+
workspaceRoot = process.cwd(),
|
|
154
|
+
scope = 'user',
|
|
155
|
+
homeDir = null
|
|
156
|
+
}) {
|
|
157
|
+
const normalizedEcosystem = normalizeString(ecosystem).toLowerCase();
|
|
158
|
+
const normalizedScope = normalizeString(scope, 'user').toLowerCase();
|
|
159
|
+
const resolvedWorkspaceRoot = path.resolve(workspaceRoot || process.cwd());
|
|
160
|
+
const resolvedHomeDir = resolveHomeDir(homeDir);
|
|
161
|
+
|
|
162
|
+
if (normalizedEcosystem === 'codex') {
|
|
163
|
+
return {
|
|
164
|
+
ecosystem: normalizedEcosystem,
|
|
165
|
+
scope: 'user',
|
|
166
|
+
format: 'toml',
|
|
167
|
+
targetFile: path.join(resolvedHomeDir, '.codex', 'config.toml')
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (normalizedEcosystem === 'claude_code') {
|
|
172
|
+
if (normalizedScope === 'project') {
|
|
173
|
+
return {
|
|
174
|
+
ecosystem: normalizedEcosystem,
|
|
175
|
+
scope: 'project',
|
|
176
|
+
format: 'json',
|
|
177
|
+
targetFile: path.join(resolvedWorkspaceRoot, '.mcp.json')
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
ecosystem: normalizedEcosystem,
|
|
182
|
+
scope: 'user',
|
|
183
|
+
format: 'json',
|
|
184
|
+
targetFile: path.join(resolvedHomeDir, '.claude.json')
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new Error(`Unsupported reference connector ecosystem "${ecosystem}"`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function buildCodexManagedBlock(serverName, serverDefinition) {
|
|
192
|
+
const lines = [
|
|
193
|
+
`${MANAGED_BLOCK_PREFIX} ${serverName}`,
|
|
194
|
+
`[mcp_servers.${serverName}]`,
|
|
195
|
+
`command = "${escapeTomlString(serverDefinition.command)}"`,
|
|
196
|
+
`args = ${formatTomlArray(serverDefinition.args || [])}`,
|
|
197
|
+
`enabled = ${serverDefinition.enabled === false ? 'false' : 'true'}`
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const envEntries = Object.entries(serverDefinition.env || {});
|
|
201
|
+
if (envEntries.length > 0) {
|
|
202
|
+
lines.push('', `[mcp_servers.${serverName}.env]`);
|
|
203
|
+
for (const [key, value] of envEntries) {
|
|
204
|
+
lines.push(`${key} = "${escapeTomlString(value)}"`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
lines.push(MANAGED_BLOCK_SUFFIX);
|
|
209
|
+
return `${lines.join('\n')}\n`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function replaceManagedBlock(existingContent, serverName, nextBlock) {
|
|
213
|
+
const startMarker = `${MANAGED_BLOCK_PREFIX} ${serverName}`;
|
|
214
|
+
const endMarker = MANAGED_BLOCK_SUFFIX;
|
|
215
|
+
const pattern = new RegExp(
|
|
216
|
+
`${startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
|
|
217
|
+
'g'
|
|
218
|
+
);
|
|
219
|
+
const withoutPrevious = String(existingContent || '').replace(pattern, '').trimEnd();
|
|
220
|
+
if (!withoutPrevious) {
|
|
221
|
+
return nextBlock;
|
|
222
|
+
}
|
|
223
|
+
return `${withoutPrevious}\n\n${nextBlock}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function mergeCodexConfig(existingContent, serverName, serverDefinition) {
|
|
227
|
+
return replaceManagedBlock(existingContent, serverName, buildCodexManagedBlock(serverName, serverDefinition));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function mergeClaudeCodeConfig(existingConfig, serverName, serverDefinition) {
|
|
231
|
+
const nextConfig = existingConfig && typeof existingConfig === 'object' ? { ...existingConfig } : {};
|
|
232
|
+
nextConfig.mcpServers = nextConfig.mcpServers && typeof nextConfig.mcpServers === 'object'
|
|
233
|
+
? { ...nextConfig.mcpServers }
|
|
234
|
+
: {};
|
|
235
|
+
nextConfig.mcpServers[serverName] = {
|
|
236
|
+
command: serverDefinition.command,
|
|
237
|
+
args: [...(serverDefinition.args || [])],
|
|
238
|
+
env: { ...(serverDefinition.env || {}) }
|
|
239
|
+
};
|
|
240
|
+
return nextConfig;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function installReferenceConnector({
|
|
244
|
+
ecosystem,
|
|
245
|
+
cloudUrl,
|
|
246
|
+
cloudApiKey,
|
|
247
|
+
workspaceRoot = process.cwd(),
|
|
248
|
+
clientProfileId,
|
|
249
|
+
connectorId = '',
|
|
250
|
+
connectorType = '',
|
|
251
|
+
workspaceId = '',
|
|
252
|
+
packageRoot = null,
|
|
253
|
+
runtime = 'npm',
|
|
254
|
+
scope = 'user',
|
|
255
|
+
serverName = DEFAULT_SERVER_NAME,
|
|
256
|
+
homeDir = null,
|
|
257
|
+
dryRun = false
|
|
258
|
+
}) {
|
|
259
|
+
const normalizedServerName = sanitizeServerName(serverName);
|
|
260
|
+
const target = getReferenceConnectorTarget({
|
|
261
|
+
ecosystem,
|
|
262
|
+
workspaceRoot,
|
|
263
|
+
scope,
|
|
264
|
+
homeDir
|
|
265
|
+
});
|
|
266
|
+
const serverDefinition = buildReferenceConnectorServer({
|
|
267
|
+
ecosystem,
|
|
268
|
+
cloudUrl,
|
|
269
|
+
cloudApiKey,
|
|
270
|
+
workspaceRoot,
|
|
271
|
+
clientProfileId,
|
|
272
|
+
connectorId,
|
|
273
|
+
connectorType,
|
|
274
|
+
workspaceId,
|
|
275
|
+
packageRoot,
|
|
276
|
+
runtime
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
let renderedContent = '';
|
|
280
|
+
if (target.format === 'toml') {
|
|
281
|
+
const existingContent = fs.existsSync(target.targetFile)
|
|
282
|
+
? fs.readFileSync(target.targetFile, 'utf8')
|
|
283
|
+
: '';
|
|
284
|
+
renderedContent = mergeCodexConfig(existingContent, normalizedServerName, serverDefinition);
|
|
285
|
+
} else {
|
|
286
|
+
const existingConfig = readJson(target.targetFile, {});
|
|
287
|
+
renderedContent = JSON.stringify(
|
|
288
|
+
mergeClaudeCodeConfig(existingConfig, normalizedServerName, serverDefinition),
|
|
289
|
+
null,
|
|
290
|
+
2
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!dryRun) {
|
|
295
|
+
ensureDir(target.targetFile);
|
|
296
|
+
fs.writeFileSync(target.targetFile, renderedContent, 'utf8');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
ecosystem: target.ecosystem,
|
|
301
|
+
scope: target.scope,
|
|
302
|
+
targetFile: target.targetFile,
|
|
303
|
+
serverName: normalizedServerName,
|
|
304
|
+
serverDefinition,
|
|
305
|
+
renderedContent,
|
|
306
|
+
written: !dryRun
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function printReferenceConnectorConfig(options) {
|
|
311
|
+
return installReferenceConnector({
|
|
312
|
+
...options,
|
|
313
|
+
dryRun: true
|
|
314
|
+
});
|
|
315
|
+
}
|
package/src/router.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export class SmartRouter {
|
|
6
|
-
constructor(cloudUrl, cloudApiKey, localTools, workspaceRoot = null) {
|
|
6
|
+
constructor(cloudUrl, cloudApiKey, localTools, workspaceRoot = null, deviceIdentity = null) {
|
|
7
7
|
this.cloudUrl = cloudUrl.replace(/\/$/, ''); // 移除尾部斜杠
|
|
8
8
|
this.cloudApiKey = cloudApiKey;
|
|
9
9
|
this.localTools = localTools;
|
|
10
10
|
this.workspaceRoot = workspaceRoot || process.cwd();
|
|
11
|
+
this.deviceIdentity = deviceIdentity;
|
|
11
12
|
|
|
12
13
|
// 工具路由规则
|
|
13
14
|
this.routingRules = {
|
|
@@ -27,7 +28,12 @@ export class SmartRouter {
|
|
|
27
28
|
'get_document_symbols',
|
|
28
29
|
'replace_lines',
|
|
29
30
|
'execute_command',
|
|
30
|
-
'execute_shell_command'
|
|
31
|
+
'execute_shell_command',
|
|
32
|
+
'connector_bridge_status',
|
|
33
|
+
'connector_sync_profile',
|
|
34
|
+
'connector_report_status',
|
|
35
|
+
'connector_discover_project_probe',
|
|
36
|
+
'connector_report_project_probe'
|
|
31
37
|
],
|
|
32
38
|
|
|
33
39
|
// 云端工具(需要网络或云端资源)
|
|
@@ -151,21 +157,26 @@ export class SmartRouter {
|
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
try {
|
|
160
|
+
const body = JSON.stringify({
|
|
161
|
+
jsonrpc: '2.0',
|
|
162
|
+
id: Date.now(),
|
|
163
|
+
method: 'tools/call',
|
|
164
|
+
params: {
|
|
165
|
+
name: toolName,
|
|
166
|
+
arguments: params
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const signedHeaders = this.deviceIdentity
|
|
170
|
+
? await this.deviceIdentity.buildSignedHeaders({ method: 'POST', pathname: '/mcp', body })
|
|
171
|
+
: {};
|
|
154
172
|
const response = await fetch(`${this.cloudUrl}/mcp`, {
|
|
155
173
|
method: 'POST',
|
|
156
174
|
headers: {
|
|
157
175
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
158
|
-
'Content-Type': 'application/json'
|
|
176
|
+
'Content-Type': 'application/json',
|
|
177
|
+
...signedHeaders
|
|
159
178
|
},
|
|
160
|
-
body
|
|
161
|
-
jsonrpc: '2.0',
|
|
162
|
-
id: Date.now(),
|
|
163
|
-
method: 'tools/call',
|
|
164
|
-
params: {
|
|
165
|
-
name: toolName,
|
|
166
|
-
arguments: params
|
|
167
|
-
}
|
|
168
|
-
})
|
|
179
|
+
body
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
if (!response.ok) {
|
|
@@ -263,18 +274,23 @@ export class SmartRouter {
|
|
|
263
274
|
*/
|
|
264
275
|
async getCloudTools() {
|
|
265
276
|
try {
|
|
277
|
+
const body = JSON.stringify({
|
|
278
|
+
jsonrpc: '2.0',
|
|
279
|
+
id: Date.now(),
|
|
280
|
+
method: 'tools/list',
|
|
281
|
+
params: {}
|
|
282
|
+
});
|
|
283
|
+
const signedHeaders = this.deviceIdentity
|
|
284
|
+
? await this.deviceIdentity.buildSignedHeaders({ method: 'POST', pathname: '/mcp', body })
|
|
285
|
+
: {};
|
|
266
286
|
const response = await fetch(`${this.cloudUrl}/mcp`, {
|
|
267
287
|
method: 'POST',
|
|
268
288
|
headers: {
|
|
269
289
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
270
|
-
'Content-Type': 'application/json'
|
|
290
|
+
'Content-Type': 'application/json',
|
|
291
|
+
...signedHeaders
|
|
271
292
|
},
|
|
272
|
-
body
|
|
273
|
-
jsonrpc: '2.0',
|
|
274
|
-
id: Date.now(),
|
|
275
|
-
method: 'tools/list',
|
|
276
|
-
params: {}
|
|
277
|
-
})
|
|
293
|
+
body
|
|
278
294
|
});
|
|
279
295
|
|
|
280
296
|
if (!response.ok) {
|
|
@@ -294,4 +310,3 @@ export class SmartRouter {
|
|
|
294
310
|
}
|
|
295
311
|
}
|
|
296
312
|
}
|
|
297
|
-
|
package/package-personal.json
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cloudflare-mcp-smart-proxy",
|
|
3
|
-
"version": "1.1.1",
|
|
4
|
-
"description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"cloudflare-mcp-proxy": "./index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"mcp",
|
|
15
|
-
"cloudflare",
|
|
16
|
-
"proxy",
|
|
17
|
-
"local",
|
|
18
|
-
"filesystem"
|
|
19
|
-
],
|
|
20
|
-
"author": "",
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
24
|
-
},
|
|
25
|
-
"engines": {
|
|
26
|
-
"node": ">=18.0.0"
|
|
27
|
-
}
|
|
28
|
-
}
|