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 CHANGED
@@ -1,188 +1,120 @@
1
- # @cloudflare-mcp/smart-proxy
1
+ # CLOUDMCP Local Proxy
2
2
 
3
- 智能路由代理,自动将 MCP 工具请求路由到云端或本地执行。
3
+ `local-proxy` `CLOUDMCP` 当前主线的参考连接器实现。
4
4
 
5
- ## 功能特性
5
+ 它包含两层能力:
6
6
 
7
- - ✅ **智能路由**: 自动判断工具应该路由到云端还是本地
8
- - **本地文件编辑**: 支持读取、写入、差异编辑本地文件
9
- - ✅ **云端工具集成**: 无缝使用 Cloudflare MCP 的云端工具
10
- - **统一接口**: AI 看到的是合并后的工具列表
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
- - `read_file` - 读取本地文件
18
- - `write_file` - 写入本地文件
19
- - `edit_file` - 差异编辑本地文件
20
- - `list_dir` - 列出本地目录
21
- - `delete_file` - 删除本地文件
22
- - `execute_command` - 执行本地命令
17
+ ## 当前入口
23
18
 
24
- ### 云端工具(转发到 Cloudflare Workers)
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
- chmod +x install.sh
45
- ./install.sh
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
- ### 方法 2: 手动配置
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
- "mcpServers": {
58
- "cloudflare-mcp": {
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
- 2. 重启 Cursor IDE
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
- ```json
87
- {
88
- "tool": "read_file",
89
- "params": {
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
- ```json
100
- {
101
- "tool": "edit_file",
102
- "params": {
103
- "path": "src/index.js",
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
- ```json
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
- ### 问题 1: 无法连接到云端服务器
72
+ ## 默认注入环境变量
140
73
 
141
- **检查**:
142
- 1. 确认 `CLOUDFLARE_MCP_URL` 正确
143
- 2. 确认 `CLOUDFLARE_MCP_API_KEY` 有效
144
- 3. 检查网络连接
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
- ### 问题 2: 本地文件操作失败
82
+ 这些字段会让连接器启动后直接进入:
147
83
 
148
- **检查**:
149
- 1. 确认文件路径在工作目录内
150
- 2. 检查文件权限
151
- 3. 确认路径不包含 `..`
84
+ - `PUT /connectors/workspace-binding`
85
+ - `GET /connectors/install-plan`
86
+ - `POST /connectors/status-reports`
87
+ - `POST /connectors/project-probes`
152
88
 
153
- ### 问题 3: Cursor 无法识别服务器
89
+ 对应的正式协议定义见:
154
90
 
155
- **检查**:
156
- 1. 重启 Cursor IDE
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
- cd local-proxy
166
- npm install
167
- export CLOUDFLARE_MCP_URL="https://your-worker.workers.dev"
168
- export CLOUDFLARE_MCP_API_KEY="your_key"
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
- ### 发布到 npm
105
+ ### 本地 smoke
174
106
 
175
107
  ```bash
176
- npm login
177
- npm publish --access public
108
+ npm run smoke:connectors
178
109
  ```
179
110
 
180
- ## 许可证
111
+ ## 验证
181
112
 
182
- MIT
183
-
184
- ## 相关链接
113
+ ```bash
114
+ node --test /home/coder/project/CLOUDMCP/tests/reference-connector-a3.test.js
115
+ ```
185
116
 
186
- - [Cloudflare MCP 主项目](../README.md)
187
- - [MCP 协议文档](https://modelcontextprotocol.io/)
117
+ ## 备注
188
118
 
119
+ - 旧的 Cursor / Claude Desktop 入口仍保留兼容,但它们不再是 A3 主线。
120
+ - A3 没有把 `systemPrompt` 直接写进项目根级 `AGENTS.md` 或 `CLAUDE.md`,以避免覆盖用户已有 agent 规范文件。
@@ -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 localTools = new LocalToolExecutor(WORKSPACE_ROOT);
35
- const router = new SmartRouter(CLOUD_URL, CLOUD_API_KEY, localTools);
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.1.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
+ }