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/README.md
CHANGED
|
@@ -1,188 +1,120 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CLOUDMCP Local Proxy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`local-proxy` 是 `CLOUDMCP` 当前主线的参考连接器实现。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
它包含两层能力:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- ✅ **零配置**: 通过 npx 自动安装和使用
|
|
7
|
+
- `index.js`
|
|
8
|
+
- stdio MCP server,负责把本地工具和云端 `CLOUDMCP` 工具统一暴露给宿主生态
|
|
9
|
+
- `connector-cli.js`
|
|
10
|
+
- 参考连接器安装器,负责把这条 stdio 入口写进目标生态的原生配置文件
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
自 A3 起,官方优先支持两个生态:
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
- `codex`
|
|
15
|
+
- `claude_code`
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- `write_file` - 写入本地文件
|
|
19
|
-
- `edit_file` - 差异编辑本地文件
|
|
20
|
-
- `list_dir` - 列出本地目录
|
|
21
|
-
- `delete_file` - 删除本地文件
|
|
22
|
-
- `execute_command` - 执行本地命令
|
|
17
|
+
## 当前入口
|
|
23
18
|
|
|
24
|
-
###
|
|
25
|
-
|
|
26
|
-
- `web_search` - 网络搜索
|
|
27
|
-
- `github_*` - GitHub API 操作
|
|
28
|
-
- `memory_*` - 记忆工具
|
|
29
|
-
- `library_docs` - 库文档查询
|
|
30
|
-
- `codebase_search` - R2 代码库搜索
|
|
31
|
-
- 其他所有未匹配的工具
|
|
32
|
-
|
|
33
|
-
## 安装
|
|
34
|
-
|
|
35
|
-
### 方法 1: 使用安装脚本(推荐)
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
curl -fsSL https://raw.githubusercontent.com/your-repo/local-proxy/main/install.sh | bash
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
或下载后执行:
|
|
19
|
+
### 1. 作为 MCP stdio server 运行
|
|
42
20
|
|
|
43
21
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
22
|
+
cd /home/coder/project/CLOUDMCP/local-proxy
|
|
23
|
+
export CLOUDFLARE_MCP_URL="https://your-cloudmcp.example.com"
|
|
24
|
+
export CLOUDFLARE_MCP_API_KEY="your_api_key"
|
|
25
|
+
export CLOUDMCP_CLIENT_PROFILE_ID="client_profile.codex.default"
|
|
26
|
+
node index.js
|
|
46
27
|
```
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
1. 在 Cursor 配置文件中添加:
|
|
51
|
-
|
|
52
|
-
**~/.cursor/mcp.json** (macOS/Linux)
|
|
53
|
-
**%APPDATA%\Cursor\mcp.json** (Windows)
|
|
29
|
+
正式 IDE 安装默认不会把仓库内 `index.js` 路径写进用户配置,而是写入已发布包入口:
|
|
54
30
|
|
|
55
31
|
```json
|
|
56
32
|
{
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
"type": "stdio",
|
|
60
|
-
"command": "npx",
|
|
61
|
-
"args": ["-y", "@cloudflare-mcp/smart-proxy"],
|
|
62
|
-
"env": {
|
|
63
|
-
"CLOUDFLARE_MCP_URL": "https://your-worker.workers.dev",
|
|
64
|
-
"CLOUDFLARE_MCP_API_KEY": "your_api_key_here",
|
|
65
|
-
"WORKSPACE_ROOT": "/path/to/your/workspace"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "-p", "cloudflare-mcp-smart-proxy", "cloudflare-mcp-proxy"]
|
|
69
35
|
}
|
|
70
36
|
```
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## 环境变量
|
|
75
|
-
|
|
76
|
-
| 变量名 | 必需 | 说明 |
|
|
77
|
-
|--------|------|------|
|
|
78
|
-
| `CLOUDFLARE_MCP_URL` | ✅ | Cloudflare MCP 服务器 URL |
|
|
79
|
-
| `CLOUDFLARE_MCP_API_KEY` | ✅ | API Key |
|
|
80
|
-
| `WORKSPACE_ROOT` | ❌ | 工作目录根路径(默认: 当前目录) |
|
|
38
|
+
当前已发布包名与可执行名不同,因此正式入口需要显式指定包与命令。仓库内入口只保留给本地开发调试,通过 `--runtime local` 显式选择。
|
|
81
39
|
|
|
82
|
-
|
|
40
|
+
### 2. 作为参考连接器安装器运行
|
|
83
41
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"target_file": "src/index.js",
|
|
91
|
-
"offset": 0,
|
|
92
|
-
"limit": 50
|
|
93
|
-
}
|
|
94
|
-
}
|
|
42
|
+
```bash
|
|
43
|
+
cd /home/coder/project/CLOUDMCP/local-proxy
|
|
44
|
+
node connector-cli.js install codex \
|
|
45
|
+
--cloud-url https://your-cloudmcp.example.com \
|
|
46
|
+
--api-key your_api_key \
|
|
47
|
+
--client-profile-id client_profile.codex.default
|
|
95
48
|
```
|
|
96
49
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"edits": [
|
|
105
|
-
{
|
|
106
|
-
"type": "replace",
|
|
107
|
-
"startLine": 10,
|
|
108
|
-
"endLine": 12,
|
|
109
|
-
"oldText": "const x = 1;\nconst y = 2;",
|
|
110
|
-
"newText": "const x = 1;\nconst y = 3;\nconst z = 4;"
|
|
111
|
-
}
|
|
112
|
-
],
|
|
113
|
-
"originalContent": "..."
|
|
114
|
-
}
|
|
115
|
-
}
|
|
50
|
+
```bash
|
|
51
|
+
cd /home/coder/project/CLOUDMCP/local-proxy
|
|
52
|
+
node connector-cli.js install claude_code \
|
|
53
|
+
--scope project \
|
|
54
|
+
--cloud-url https://your-cloudmcp.example.com \
|
|
55
|
+
--api-key your_api_key \
|
|
56
|
+
--client-profile-id client_profile.claude_code.default
|
|
116
57
|
```
|
|
117
58
|
|
|
118
|
-
|
|
59
|
+
## 目标配置文件
|
|
119
60
|
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
"tool": "execute_command",
|
|
123
|
-
"params": {
|
|
124
|
-
"command": "npm test",
|
|
125
|
-
"cwd": "."
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
```
|
|
61
|
+
### Codex
|
|
129
62
|
|
|
130
|
-
|
|
63
|
+
- 配置文件:`~/.codex/config.toml`
|
|
64
|
+
- 写入方式:managed block,不覆盖用户其他配置
|
|
131
65
|
|
|
132
|
-
|
|
133
|
-
- ✅ 工作目录限制:只能访问指定目录内的文件
|
|
134
|
-
- ✅ 命令黑名单:禁止执行危险命令
|
|
135
|
-
- ✅ 超时保护:命令执行 30 秒超时
|
|
66
|
+
### Claude Code
|
|
136
67
|
|
|
137
|
-
|
|
68
|
+
- 项目级:`<workspace>/.mcp.json`
|
|
69
|
+
- 用户级:`~/.claude.json`
|
|
70
|
+
- 写入方式:JSON merge,不覆盖其他 `mcpServers`
|
|
138
71
|
|
|
139
|
-
|
|
72
|
+
## 默认注入环境变量
|
|
140
73
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
74
|
+
- `CLOUDFLARE_MCP_URL`
|
|
75
|
+
- `CLOUDFLARE_MCP_API_KEY`
|
|
76
|
+
- `WORKSPACE_ROOT`
|
|
77
|
+
- `CLOUDMCP_CLIENT_PROFILE_ID`
|
|
78
|
+
- `CLOUDMCP_CONNECTOR_ID`
|
|
79
|
+
- `CLOUDMCP_CONNECTOR_TYPE`
|
|
80
|
+
- `CLOUDMCP_WORKSPACE_ID`
|
|
145
81
|
|
|
146
|
-
|
|
82
|
+
这些字段会让连接器启动后直接进入:
|
|
147
83
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
84
|
+
- `PUT /connectors/workspace-binding`
|
|
85
|
+
- `GET /connectors/install-plan`
|
|
86
|
+
- `POST /connectors/status-reports`
|
|
87
|
+
- `POST /connectors/project-probes`
|
|
152
88
|
|
|
153
|
-
|
|
89
|
+
对应的正式协议定义见:
|
|
154
90
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
2. 检查配置文件 JSON 格式
|
|
158
|
-
3. 查看 Cursor 开发者工具中的错误日志
|
|
91
|
+
- [CLOUDMCP_A2_EXTERNAL_CONNECTOR_CONTRACT_STANDARD_2026-03-30.md](/home/coder/project/CLOUDMCP/docs/CLOUDMCP_A2_EXTERNAL_CONNECTOR_CONTRACT_STANDARD_2026-03-30.md)
|
|
92
|
+
- [CLOUDMCP_A3_OFFICIAL_REFERENCE_CONNECTOR_STANDARD_2026-03-30.md](/home/coder/project/CLOUDMCP/docs/CLOUDMCP_A3_OFFICIAL_REFERENCE_CONNECTOR_STANDARD_2026-03-30.md)
|
|
159
93
|
|
|
160
|
-
##
|
|
94
|
+
## 常用命令
|
|
161
95
|
|
|
162
|
-
###
|
|
96
|
+
### Dry-run 渲染配置
|
|
163
97
|
|
|
164
98
|
```bash
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
export WORKSPACE_ROOT="/path/to/workspace"
|
|
170
|
-
node index.js
|
|
99
|
+
node connector-cli.js print codex \
|
|
100
|
+
--cloud-url https://cloudmcp.example.com \
|
|
101
|
+
--api-key test-key \
|
|
102
|
+
--client-profile-id client_profile.codex.default
|
|
171
103
|
```
|
|
172
104
|
|
|
173
|
-
###
|
|
105
|
+
### 本地 smoke
|
|
174
106
|
|
|
175
107
|
```bash
|
|
176
|
-
npm
|
|
177
|
-
npm publish --access public
|
|
108
|
+
npm run smoke:connectors
|
|
178
109
|
```
|
|
179
110
|
|
|
180
|
-
##
|
|
111
|
+
## 验证
|
|
181
112
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
113
|
+
```bash
|
|
114
|
+
node --test /home/coder/project/CLOUDMCP/tests/reference-connector-a3.test.js
|
|
115
|
+
```
|
|
185
116
|
|
|
186
|
-
|
|
187
|
-
- [MCP 协议文档](https://modelcontextprotocol.io/)
|
|
117
|
+
## 备注
|
|
188
118
|
|
|
119
|
+
- 旧的 Cursor / Claude Desktop 入口仍保留兼容,但它们不再是 A3 主线。
|
|
120
|
+
- A3 没有把 `systemPrompt` 直接写进项目根级 `AGENTS.md` 或 `CLAUDE.md`,以避免覆盖用户已有 agent 规范文件。
|
package/connector-cli.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { installReferenceConnector, printReferenceConnectorConfig } from './src/reference-connectors.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
function printUsage() {
|
|
11
|
+
console.error('Usage: cloudmcp-connector <install|print|smoke> <codex|claude_code> [options]');
|
|
12
|
+
console.error('');
|
|
13
|
+
console.error('Required options:');
|
|
14
|
+
console.error(' --cloud-url <url>');
|
|
15
|
+
console.error(' --api-key <key>');
|
|
16
|
+
console.error(' --client-profile-id <id>');
|
|
17
|
+
console.error('');
|
|
18
|
+
console.error('Optional options:');
|
|
19
|
+
console.error(' --workspace-root <path> Default: current directory');
|
|
20
|
+
console.error(' --workspace-id <id>');
|
|
21
|
+
console.error(' --connector-id <id>');
|
|
22
|
+
console.error(' --connector-type <type> Default: ecosystem name');
|
|
23
|
+
console.error(' --scope <user|project> Claude Code supports user/project; Codex is always user');
|
|
24
|
+
console.error(' --server-name <name> Default: cloudmcp');
|
|
25
|
+
console.error(' --runtime <npm|local> Default: npm');
|
|
26
|
+
console.error(' --dry-run Show output without writing');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const [command = '', ecosystem = '', ...rest] = argv;
|
|
31
|
+
const options = {
|
|
32
|
+
command,
|
|
33
|
+
ecosystem
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
37
|
+
const token = rest[index];
|
|
38
|
+
if (!token.startsWith('--')) {
|
|
39
|
+
throw new Error(`Unexpected argument "${token}"`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const key = token.slice(2);
|
|
43
|
+
if (key === 'dry-run') {
|
|
44
|
+
options.dryRun = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const value = rest[index + 1];
|
|
49
|
+
if (value == null || value.startsWith('--')) {
|
|
50
|
+
throw new Error(`Missing value for --${key}`);
|
|
51
|
+
}
|
|
52
|
+
options[key] = value;
|
|
53
|
+
index += 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return options;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveRequiredOption(options, flagName, envNames = []) {
|
|
60
|
+
const directValue = options[flagName];
|
|
61
|
+
if (directValue) return directValue;
|
|
62
|
+
for (const envName of envNames) {
|
|
63
|
+
if (process.env[envName]) return process.env[envName];
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Missing required option --${flagName}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildInstallOptions(options) {
|
|
69
|
+
const workspaceRoot = path.resolve(options['workspace-root'] || process.cwd());
|
|
70
|
+
return {
|
|
71
|
+
ecosystem: options.ecosystem,
|
|
72
|
+
cloudUrl: resolveRequiredOption(options, 'cloud-url', ['CLOUDFLARE_MCP_URL', 'MCP_URL']),
|
|
73
|
+
cloudApiKey: resolveRequiredOption(options, 'api-key', ['CLOUDFLARE_MCP_API_KEY', 'MCP_API_KEY']),
|
|
74
|
+
clientProfileId: resolveRequiredOption(options, 'client-profile-id', ['CLOUDMCP_CLIENT_PROFILE_ID', 'CLIENT_PROFILE_ID']),
|
|
75
|
+
workspaceRoot,
|
|
76
|
+
workspaceId: options['workspace-id'] || process.env.CLOUDMCP_WORKSPACE_ID || process.env.WORKSPACE_ID || '',
|
|
77
|
+
connectorId: options['connector-id'] || process.env.CLOUDMCP_CONNECTOR_ID || process.env.CONNECTOR_ID || '',
|
|
78
|
+
connectorType: options['connector-type'] || process.env.CLOUDMCP_CONNECTOR_TYPE || process.env.CONNECTOR_TYPE || '',
|
|
79
|
+
scope: options.scope || 'user',
|
|
80
|
+
serverName: options['server-name'] || 'cloudmcp',
|
|
81
|
+
runtime: options.runtime || 'npm',
|
|
82
|
+
packageRoot: __dirname,
|
|
83
|
+
dryRun: options.dryRun === true
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function printInstallResult(result, { includeContent = false } = {}) {
|
|
88
|
+
console.error(`ecosystem: ${result.ecosystem}`);
|
|
89
|
+
console.error(`scope: ${result.scope}`);
|
|
90
|
+
console.error(`target: ${result.targetFile}`);
|
|
91
|
+
console.error(`server: ${result.serverName}`);
|
|
92
|
+
console.error(`written: ${result.written ? 'yes' : 'no'}`);
|
|
93
|
+
if (includeContent) {
|
|
94
|
+
process.stdout.write(`${result.renderedContent}\n`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function runSmoke(ecosystem, options) {
|
|
99
|
+
const result = printReferenceConnectorConfig({
|
|
100
|
+
...buildInstallOptions(options),
|
|
101
|
+
ecosystem
|
|
102
|
+
});
|
|
103
|
+
if (!result.renderedContent.includes(result.serverName)) {
|
|
104
|
+
throw new Error(`Smoke failed: missing server name in rendered content for ${ecosystem}`);
|
|
105
|
+
}
|
|
106
|
+
if (!result.renderedContent.includes('CLOUDMCP_CLIENT_PROFILE_ID')) {
|
|
107
|
+
throw new Error(`Smoke failed: missing connector env in rendered content for ${ecosystem}`);
|
|
108
|
+
}
|
|
109
|
+
printInstallResult(result, { includeContent: true });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
try {
|
|
114
|
+
const options = parseArgs(process.argv.slice(2));
|
|
115
|
+
|
|
116
|
+
if (!['install', 'print', 'smoke'].includes(options.command)) {
|
|
117
|
+
printUsage();
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
if (!['codex', 'claude_code'].includes(options.ecosystem)) {
|
|
121
|
+
printUsage();
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (options.command === 'install') {
|
|
126
|
+
const result = installReferenceConnector(buildInstallOptions(options));
|
|
127
|
+
printInstallResult(result);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.command === 'print') {
|
|
132
|
+
const result = printReferenceConnectorConfig(buildInstallOptions(options));
|
|
133
|
+
printInstallResult(result, { includeContent: true });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await runSmoke(options.ecosystem, options);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`cloudmcp-connector error: ${error.message}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
package/index.js
CHANGED
|
@@ -15,11 +15,20 @@ import {
|
|
|
15
15
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
16
16
|
import { SmartRouter } from './src/router.js';
|
|
17
17
|
import { LocalToolExecutor } from './src/local-tools.js';
|
|
18
|
+
import { ConnectorBridge } from './src/connector-bridge.js';
|
|
18
19
|
|
|
19
20
|
// 从环境变量读取配置
|
|
20
21
|
const CLOUD_URL = process.env.CLOUDFLARE_MCP_URL || process.env.MCP_URL;
|
|
21
22
|
const CLOUD_API_KEY = process.env.CLOUDFLARE_MCP_API_KEY || process.env.MCP_API_KEY;
|
|
22
23
|
const WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || process.cwd();
|
|
24
|
+
const CLIENT_PROFILE_ID = process.env.CLOUDMCP_CLIENT_PROFILE_ID || process.env.CLIENT_PROFILE_ID || '';
|
|
25
|
+
const CONNECTOR_ID = process.env.CLOUDMCP_CONNECTOR_ID || process.env.CONNECTOR_ID || '';
|
|
26
|
+
const CONNECTOR_TYPE = process.env.CLOUDMCP_CONNECTOR_TYPE || process.env.CONNECTOR_TYPE || 'smart_proxy';
|
|
27
|
+
const WORKSPACE_ID = process.env.CLOUDMCP_WORKSPACE_ID || process.env.WORKSPACE_ID || '';
|
|
28
|
+
const AUTO_SYNC_PROFILE = (process.env.CLOUDMCP_AUTO_SYNC_PROFILE || 'true') !== 'false';
|
|
29
|
+
const AUTO_APPLY_BRAIN = (process.env.CLOUDMCP_AUTO_APPLY_BRAIN || 'true') !== 'false';
|
|
30
|
+
const AUTO_REPORT_PROJECT_PROBE = (process.env.CLOUDMCP_AUTO_REPORT_PROJECT_PROBE || 'true') === 'true';
|
|
31
|
+
const AUTO_GENERATE_CONTEXT_PACK = (process.env.CLOUDMCP_AUTO_GENERATE_CONTEXT_PACK || 'true') === 'true';
|
|
23
32
|
|
|
24
33
|
if (!CLOUD_URL || !CLOUD_API_KEY) {
|
|
25
34
|
console.error('Error: CLOUDFLARE_MCP_URL and CLOUDFLARE_MCP_API_KEY environment variables are required');
|
|
@@ -31,8 +40,17 @@ if (!CLOUD_URL || !CLOUD_API_KEY) {
|
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
// 初始化组件
|
|
34
|
-
const
|
|
35
|
-
|
|
43
|
+
const connectorBridge = new ConnectorBridge({
|
|
44
|
+
cloudUrl: CLOUD_URL,
|
|
45
|
+
cloudApiKey: CLOUD_API_KEY,
|
|
46
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
47
|
+
clientProfileId: CLIENT_PROFILE_ID,
|
|
48
|
+
connectorId: CONNECTOR_ID,
|
|
49
|
+
connectorType: CONNECTOR_TYPE,
|
|
50
|
+
workspaceId: WORKSPACE_ID
|
|
51
|
+
});
|
|
52
|
+
const localTools = new LocalToolExecutor(WORKSPACE_ROOT, connectorBridge);
|
|
53
|
+
const router = new SmartRouter(CLOUD_URL, CLOUD_API_KEY, localTools, WORKSPACE_ROOT, connectorBridge.deviceIdentity);
|
|
36
54
|
|
|
37
55
|
// 创建 MCP 服务器
|
|
38
56
|
const server = new Server(
|
|
@@ -196,6 +214,23 @@ async function main() {
|
|
|
196
214
|
console.error('Cloudflare MCP Smart Proxy started');
|
|
197
215
|
console.error(`Workspace root: ${WORKSPACE_ROOT}`);
|
|
198
216
|
console.error(`Cloud URL: ${CLOUD_URL}`);
|
|
217
|
+
if (connectorBridge.isConfigured()) {
|
|
218
|
+
const bridgeStatus = await connectorBridge.initialize({
|
|
219
|
+
autoApplyBrain: AUTO_APPLY_BRAIN,
|
|
220
|
+
autoSyncProfile: AUTO_SYNC_PROFILE,
|
|
221
|
+
autoReportProjectProbe: AUTO_REPORT_PROJECT_PROBE,
|
|
222
|
+
autoGenerateContextPack: AUTO_GENERATE_CONTEXT_PACK
|
|
223
|
+
});
|
|
224
|
+
console.error(`Connector bridge profile: ${bridgeStatus.clientProfileId}`);
|
|
225
|
+
console.error(`Connector bridge workspace: ${bridgeStatus.workspaceId}`);
|
|
226
|
+
const applyResult = bridgeStatus.state?.lastBrainApplyResult;
|
|
227
|
+
if (applyResult) {
|
|
228
|
+
console.error(`Brain applied for IDE: ${applyResult.ide} (${applyResult.applied?.length || 0} files written)`);
|
|
229
|
+
}
|
|
230
|
+
console.error(`Connector bridge auto probe: ${AUTO_REPORT_PROJECT_PROBE ? 'enabled' : 'disabled'}`);
|
|
231
|
+
} else {
|
|
232
|
+
console.error('Connector bridge profile: not configured');
|
|
233
|
+
}
|
|
199
234
|
} catch (error) {
|
|
200
235
|
console.error('Failed to start server:', error);
|
|
201
236
|
process.exit(1);
|
|
@@ -206,4 +241,3 @@ main().catch((error) => {
|
|
|
206
241
|
console.error('Fatal error:', error);
|
|
207
242
|
process.exit(1);
|
|
208
243
|
});
|
|
209
|
-
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-mcp-smart-proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.33.0",
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"bin": {
|
|
8
|
-
"cloudflare-mcp-proxy": "./index.js"
|
|
9
|
+
"cloudflare-mcp-smart-proxy": "./index.js",
|
|
10
|
+
"cloudflare-mcp-proxy": "./index.js",
|
|
11
|
+
"cloudmcp-connector": "./connector-cli.js"
|
|
9
12
|
},
|
|
10
13
|
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
14
|
+
"start": "node index.js",
|
|
15
|
+
"smoke:connectors": "node connector-cli.js smoke codex --cloud-url https://cloudmcp.example.com --api-key test-key --client-profile-id client_profile.codex.default && node connector-cli.js smoke claude_code --cloud-url https://cloudmcp.example.com --api-key test-key --client-profile-id client_profile.claude_code.default --scope project"
|
|
12
16
|
},
|
|
13
17
|
"keywords": [
|
|
14
18
|
"mcp",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const CONNECTOR_CONTRACT_VERSION = 'a2.v1';
|
|
2
|
+
|
|
3
|
+
export class CloudClient {
|
|
4
|
+
constructor({ cloudUrl, cloudApiKey, deviceIdentity = null }) {
|
|
5
|
+
this.cloudUrl = String(cloudUrl || '').replace(/\/$/, '');
|
|
6
|
+
this.cloudApiKey = cloudApiKey || '';
|
|
7
|
+
this.deviceIdentity = deviceIdentity;
|
|
8
|
+
if (!this.cloudUrl) {
|
|
9
|
+
throw new Error('cloudUrl is required');
|
|
10
|
+
}
|
|
11
|
+
if (!this.cloudApiKey) {
|
|
12
|
+
throw new Error('cloudApiKey is required');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
buildUrl(path, query = null) {
|
|
17
|
+
const url = new URL(path, `${this.cloudUrl}/`);
|
|
18
|
+
if (query && typeof query === 'object') {
|
|
19
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
20
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
21
|
+
url.searchParams.set(key, String(value));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return url.toString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async request(path, { method = 'GET', body, query, idempotencyKey } = {}) {
|
|
29
|
+
const serializedBody = body == null ? '' : JSON.stringify(body);
|
|
30
|
+
const signedHeaders = this.deviceIdentity
|
|
31
|
+
? await this.deviceIdentity.buildSignedHeaders({
|
|
32
|
+
method,
|
|
33
|
+
pathname: new URL(this.buildUrl(path, query)).pathname,
|
|
34
|
+
body: serializedBody
|
|
35
|
+
})
|
|
36
|
+
: {};
|
|
37
|
+
const response = await fetch(this.buildUrl(path, query), {
|
|
38
|
+
method,
|
|
39
|
+
headers: {
|
|
40
|
+
'Authorization': `Bearer ${this.cloudApiKey}`,
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-CloudMCP-Connector-Contract-Version': CONNECTOR_CONTRACT_VERSION,
|
|
43
|
+
...signedHeaders,
|
|
44
|
+
...(idempotencyKey ? { 'X-Idempotency-Key': idempotencyKey } : {})
|
|
45
|
+
},
|
|
46
|
+
body: body == null ? undefined : serializedBody
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let data = null;
|
|
50
|
+
try {
|
|
51
|
+
data = await response.json();
|
|
52
|
+
} catch {
|
|
53
|
+
data = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const message = data?.error?.message || data?.error || `${response.status} ${response.statusText}`;
|
|
58
|
+
throw new Error(message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (data?.connectorContractVersion && data.connectorContractVersion !== CONNECTOR_CONTRACT_VERSION) {
|
|
62
|
+
throw new Error(`Unsupported connector contract version "${data.connectorContractVersion}"`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async fetchInstallPlan({ clientProfileId, workspaceId }) {
|
|
69
|
+
return this.request('/connectors/install-plan', {
|
|
70
|
+
query: { clientProfileId, workspaceId }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async listConnectorStatusReports({ clientProfileId, workspaceId, connectorId, limit = 5 }) {
|
|
75
|
+
return this.request('/connectors/status-reports', {
|
|
76
|
+
query: { clientProfileId, workspaceId, connectorId, limit }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async createConnectorStatusReport({ clientProfileId, report }) {
|
|
81
|
+
return this.request('/connectors/status-reports', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
idempotencyKey: report.idempotencyKey,
|
|
84
|
+
body: {
|
|
85
|
+
...report,
|
|
86
|
+
clientProfileId
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async fetchWorkspaceGrant({ clientProfileId, workspaceId, connectorId }) {
|
|
92
|
+
return this.request('/connectors/workspace-binding', {
|
|
93
|
+
query: { clientProfileId, workspaceId, connectorId }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async listProjectProbes({ clientProfileId, workspaceId, connectorId, status }) {
|
|
98
|
+
return this.request('/connectors/project-probes', {
|
|
99
|
+
query: { clientProfileId, workspaceId, connectorId, status }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async createProjectProbe({ clientProfileId, probe, autoGenerateContextPack = false }) {
|
|
104
|
+
return this.request('/connectors/project-probes', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
idempotencyKey: probe.idempotencyKey,
|
|
107
|
+
query: autoGenerateContextPack ? { auto_generate_context_pack: 'true', clientProfileId } : { clientProfileId },
|
|
108
|
+
body: {
|
|
109
|
+
...probe,
|
|
110
|
+
clientProfileId
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async fetchBrainSnapshot({ clientProfileId, workspaceId }) {
|
|
116
|
+
return this.request(`/admin/client-profiles/${encodeURIComponent(clientProfileId)}/brain-snapshot`, {
|
|
117
|
+
query: { workspaceId }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getConnectorContract() {
|
|
122
|
+
return this.request('/connectors/contract');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async registerDevice(payload) {
|
|
126
|
+
return this.request('/connectors/device/register', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
body: payload
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getCurrentDevice() {
|
|
133
|
+
return this.request('/connectors/device/current');
|
|
134
|
+
}
|
|
135
|
+
}
|