cloudflare-mcp-smart-proxy 1.1.1 → 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 +81 -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,10 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export class SmartRouter {
|
|
6
|
-
constructor(cloudUrl, cloudApiKey, localTools) {
|
|
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
|
+
this.workspaceRoot = workspaceRoot || process.cwd();
|
|
11
|
+
this.deviceIdentity = deviceIdentity;
|
|
10
12
|
|
|
11
13
|
// 工具路由规则
|
|
12
14
|
this.routingRules = {
|
|
@@ -26,7 +28,12 @@ export class SmartRouter {
|
|
|
26
28
|
'get_document_symbols',
|
|
27
29
|
'replace_lines',
|
|
28
30
|
'execute_command',
|
|
29
|
-
'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'
|
|
30
37
|
],
|
|
31
38
|
|
|
32
39
|
// 云端工具(需要网络或云端资源)
|
|
@@ -100,26 +107,76 @@ export class SmartRouter {
|
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
/**
|
|
111
|
+
* 提取项目根路径(从工作区根路径)
|
|
112
|
+
* 返回项目根目录名,用于项目锚定
|
|
113
|
+
*/
|
|
114
|
+
_extractProjectRootFromWorkspace() {
|
|
115
|
+
if (!this.workspaceRoot) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 规范化路径:统一使用正斜杠,移除尾部斜杠
|
|
120
|
+
let normalized = this.workspaceRoot.replace(/\\/g, '/').replace(/\/$/, '');
|
|
121
|
+
|
|
122
|
+
if (!normalized) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 提取最后一个目录名作为项目根路径
|
|
127
|
+
const parts = normalized.split('/').filter(p => p.length > 0);
|
|
128
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
103
131
|
/**
|
|
104
132
|
* 调用云端工具
|
|
105
133
|
*/
|
|
106
134
|
async callCloudTool(toolName, params) {
|
|
135
|
+
// 对于 skill_* 工具,自动注入项目根路径信息
|
|
136
|
+
if (toolName.startsWith('skill_')) {
|
|
137
|
+
// 如果参数中没有 project_root 且没有 paths,自动添加 project_root
|
|
138
|
+
if (!params.project_root && (!params.paths || params.paths.length === 0)) {
|
|
139
|
+
const projectRoot = this._extractProjectRootFromWorkspace();
|
|
140
|
+
if (projectRoot) {
|
|
141
|
+
params.project_root = projectRoot;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 如果参数中有 paths 但都是相对路径,尝试转换为相对于工作区根路径的路径
|
|
146
|
+
if (params.paths && Array.isArray(params.paths) && params.paths.length > 0) {
|
|
147
|
+
// 确保路径是相对于工作区根路径的
|
|
148
|
+
params.paths = params.paths.map(path => {
|
|
149
|
+
// 如果路径是绝对路径且在工作区根路径下,转换为相对路径
|
|
150
|
+
if (path.startsWith('/') && path.startsWith(this.workspaceRoot)) {
|
|
151
|
+
return path.replace(this.workspaceRoot + '/', '').replace(this.workspaceRoot, '');
|
|
152
|
+
}
|
|
153
|
+
// 如果路径已经是相对路径,保持不变
|
|
154
|
+
return path;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
107
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
|
+
: {};
|
|
108
172
|
const response = await fetch(`${this.cloudUrl}/mcp`, {
|
|
109
173
|
method: 'POST',
|
|
110
174
|
headers: {
|
|
111
175
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
112
|
-
'Content-Type': 'application/json'
|
|
176
|
+
'Content-Type': 'application/json',
|
|
177
|
+
...signedHeaders
|
|
113
178
|
},
|
|
114
|
-
body
|
|
115
|
-
jsonrpc: '2.0',
|
|
116
|
-
id: Date.now(),
|
|
117
|
-
method: 'tools/call',
|
|
118
|
-
params: {
|
|
119
|
-
name: toolName,
|
|
120
|
-
arguments: params
|
|
121
|
-
}
|
|
122
|
-
})
|
|
179
|
+
body
|
|
123
180
|
});
|
|
124
181
|
|
|
125
182
|
if (!response.ok) {
|
|
@@ -217,18 +274,23 @@ export class SmartRouter {
|
|
|
217
274
|
*/
|
|
218
275
|
async getCloudTools() {
|
|
219
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
|
+
: {};
|
|
220
286
|
const response = await fetch(`${this.cloudUrl}/mcp`, {
|
|
221
287
|
method: 'POST',
|
|
222
288
|
headers: {
|
|
223
289
|
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
224
|
-
'Content-Type': 'application/json'
|
|
290
|
+
'Content-Type': 'application/json',
|
|
291
|
+
...signedHeaders
|
|
225
292
|
},
|
|
226
|
-
body
|
|
227
|
-
jsonrpc: '2.0',
|
|
228
|
-
id: Date.now(),
|
|
229
|
-
method: 'tools/list',
|
|
230
|
-
params: {}
|
|
231
|
-
})
|
|
293
|
+
body
|
|
232
294
|
});
|
|
233
295
|
|
|
234
296
|
if (!response.ok) {
|
|
@@ -248,4 +310,3 @@ export class SmartRouter {
|
|
|
248
310
|
}
|
|
249
311
|
}
|
|
250
312
|
}
|
|
251
|
-
|