@szc-ft/mcp-szcd-client 0.17.0 → 0.19.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/agents/qwen-extension/agents/szcd-component-expert.md +22 -0
- package/agents/src/szcd-component-expert.md +32 -2
- package/agents/szcd-component-expert.md +22 -0
- package/agents/szcd-component-expert.qoder.md +29 -2
- package/agents/szcd-component-expert.trae.md +29 -2
- package/commands/szcd-mcp-browser-test.md +57 -0
- package/commands/szcd-mcp-feedback.md +42 -0
- package/lib/browser-engine.js +379 -0
- package/lib/chrome-finder.js +125 -0
- package/lib/visual-compare.js +189 -0
- package/local-browser-executor.js +239 -0
- package/package.json +9 -1
- package/qwen-extension/agents/szcd-component-expert.md +22 -0
- package/qwen-extension/commands/szcd-mcp-browser-test.md +57 -0
- package/qwen-extension/commands/szcd-mcp-feedback.md +42 -0
- package/qwen-extension/qwen-extension.json +1 -1
- package/qwen-extension/skills/local-api-tool/SKILL.md +14 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +153 -0
- package/qwen-extension/skills/szcd-component-helper/SKILL.md +1 -1
- package/scripts/lib/claude-code.js +15 -5
- package/scripts/lib/common.js +26 -0
- package/scripts/lib/qoder.js +20 -6
- package/scripts/lib/qwen-code.js +20 -11
- package/scripts/lib/trae-cli.js +9 -6
- package/scripts/lib/trae-ide.js +15 -9
- package/scripts/postinstall.js +1 -0
- package/scripts/update-auth.js +38 -1
- package/standard-skill/local-api-tool/SKILL.md +14 -1
- package/standard-skill/local-browser-test/SKILL.md +153 -0
- package/standard-skill/szcd-component-helper/SKILL.md +1 -1
package/scripts/lib/common.js
CHANGED
|
@@ -209,6 +209,15 @@ export function encodeClientConfig(config) {
|
|
|
209
209
|
return Buffer.from(JSON.stringify(config)).toString("base64");
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 获取 MCP_API_KEY(从用户本地配置文件读取)
|
|
214
|
+
* 用于通过 X-API-Key Header 传递给 MCP 服务器鉴权
|
|
215
|
+
*/
|
|
216
|
+
export function getApiKey() {
|
|
217
|
+
const config = loadExistingConfig();
|
|
218
|
+
return config.MCP_API_KEY || "";
|
|
219
|
+
}
|
|
220
|
+
|
|
212
221
|
/**
|
|
213
222
|
* 获取 X-Client-Config Header 值(便捷函数)
|
|
214
223
|
* 返回 Base64 编码的字符串,或 null(无 CODING 配置时)
|
|
@@ -216,3 +225,20 @@ export function encodeClientConfig(config) {
|
|
|
216
225
|
export function getClientConfigHeader() {
|
|
217
226
|
return encodeClientConfig(loadClientCodingConfig());
|
|
218
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 构建 MCP HTTP 请求的 headers 对象(包含 X-Client-Config 和 X-API-Key)
|
|
231
|
+
* @returns {Object} headers 对象,可能为空对象
|
|
232
|
+
*/
|
|
233
|
+
export function buildMcpHeaders() {
|
|
234
|
+
const headers = {};
|
|
235
|
+
const clientConfigHeader = getClientConfigHeader();
|
|
236
|
+
if (clientConfigHeader) {
|
|
237
|
+
headers["X-Client-Config"] = clientConfigHeader;
|
|
238
|
+
}
|
|
239
|
+
const apiKey = getApiKey();
|
|
240
|
+
if (apiKey) {
|
|
241
|
+
headers["X-API-Key"] = apiKey;
|
|
242
|
+
}
|
|
243
|
+
return Object.keys(headers).length > 0 ? headers : null;
|
|
244
|
+
}
|
package/scripts/lib/qoder.js
CHANGED
|
@@ -29,7 +29,7 @@ import fs from "node:fs";
|
|
|
29
29
|
import path from "node:path";
|
|
30
30
|
import os from "node:os";
|
|
31
31
|
import { execSync } from "node:child_process";
|
|
32
|
-
import { getClientConfigHeader as _getClientConfigHeader } from "./common.js";
|
|
32
|
+
import { getClientConfigHeader as _getClientConfigHeader, getApiKey as _getApiKey } from "./common.js";
|
|
33
33
|
|
|
34
34
|
// ==================== 路径工具 ====================
|
|
35
35
|
|
|
@@ -150,14 +150,19 @@ function syncQoderSettings(deps) {
|
|
|
150
150
|
if (!settings.mcpServers) settings.mcpServers = {};
|
|
151
151
|
|
|
152
152
|
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
153
|
+
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
154
|
+
const headers = {};
|
|
155
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
156
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
153
157
|
const desiredConfig = {
|
|
154
158
|
type: "http",
|
|
155
159
|
url: mcpUrl,
|
|
156
|
-
...(
|
|
160
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
157
161
|
};
|
|
158
162
|
|
|
159
163
|
const current = settings.mcpServers[serverName];
|
|
160
|
-
|
|
164
|
+
const needsApiKeyUpdate = apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== apiKey);
|
|
165
|
+
if (current && current.type === "http" && current.url === mcpUrl && !needsApiKeyUpdate) {
|
|
161
166
|
console.log(`✓ Qoder ~/.qoder/settings.json already up-to-date: ${serverName} (${mcpUrl})`);
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
@@ -366,10 +371,14 @@ function setupQoderProjectConfig(deps) {
|
|
|
366
371
|
if (!settings.mcpServers) settings.mcpServers = {};
|
|
367
372
|
|
|
368
373
|
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
374
|
+
const _apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
375
|
+
const _headers = {};
|
|
376
|
+
if (clientConfigHeader) _headers["X-Client-Config"] = clientConfigHeader;
|
|
377
|
+
if (_apiKey) _headers["X-API-Key"] = _apiKey;
|
|
369
378
|
settings.mcpServers[serverName] = {
|
|
370
379
|
type: "http",
|
|
371
380
|
url: mcpUrl,
|
|
372
|
-
...(
|
|
381
|
+
...(Object.keys(_headers).length > 0 ? { headers: _headers } : {}),
|
|
373
382
|
};
|
|
374
383
|
|
|
375
384
|
writeJsonFile(projectSettingsPath, settings);
|
|
@@ -444,16 +453,21 @@ function syncQoderSettingsDirect(targetUrl, serverName) {
|
|
|
444
453
|
if (!settings.mcpServers) settings.mcpServers = {};
|
|
445
454
|
|
|
446
455
|
const current = settings.mcpServers[serverName];
|
|
447
|
-
|
|
456
|
+
const _apiKey = _getApiKey();
|
|
457
|
+
const needsApiKeyUpdate = _apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== _apiKey);
|
|
458
|
+
if (current && current.type === "http" && current.url === mcpUrl && !needsApiKeyUpdate) {
|
|
448
459
|
console.log(`⏭️ Qoder ~/.qoder/settings.json already up-to-date: ${serverName} (${mcpUrl})`);
|
|
449
460
|
return;
|
|
450
461
|
}
|
|
451
462
|
|
|
452
463
|
const clientConfigHeader = _getClientConfigHeader();
|
|
464
|
+
const _headers = {};
|
|
465
|
+
if (clientConfigHeader) _headers["X-Client-Config"] = clientConfigHeader;
|
|
466
|
+
if (_apiKey) _headers["X-API-Key"] = _apiKey;
|
|
453
467
|
settings.mcpServers[serverName] = {
|
|
454
468
|
type: "http",
|
|
455
469
|
url: mcpUrl,
|
|
456
|
-
...(
|
|
470
|
+
...(Object.keys(_headers).length > 0 ? { headers: _headers } : {}),
|
|
457
471
|
};
|
|
458
472
|
|
|
459
473
|
writeJsonFile(settingsPath, settings);
|
package/scripts/lib/qwen-code.js
CHANGED
|
@@ -23,7 +23,7 @@ import fs from "node:fs";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import os from "node:os";
|
|
25
25
|
import { execSync } from "node:child_process";
|
|
26
|
-
import { getClientConfigHeader as getClientConfigHeaderDirect } from "./common.js";
|
|
26
|
+
import { getClientConfigHeader as getClientConfigHeaderDirect, getApiKey as getApiKeyDirect } from "./common.js";
|
|
27
27
|
|
|
28
28
|
// ==================== 路径工具 ====================
|
|
29
29
|
|
|
@@ -314,13 +314,17 @@ function injectClientConfigHeader(serverName, clientConfigHeader) {
|
|
|
314
314
|
if (!manifest.mcpServers || !manifest.mcpServers[serverName]) return;
|
|
315
315
|
|
|
316
316
|
const entry = manifest.mcpServers[serverName];
|
|
317
|
-
|
|
317
|
+
const _apiKey = getApiKeyDirect();
|
|
318
|
+
if (entry.headers && entry.headers["X-Client-Config"] === clientConfigHeader && entry.headers["X-API-Key"] === _apiKey) {
|
|
318
319
|
return; // 已是最新
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
|
|
322
|
+
const _headers = {};
|
|
323
|
+
if (clientConfigHeader) _headers["X-Client-Config"] = clientConfigHeader;
|
|
324
|
+
if (_apiKey) _headers["X-API-Key"] = _apiKey;
|
|
325
|
+
entry.headers = _headers;
|
|
322
326
|
writeJsonFile(manifestPath, manifest);
|
|
323
|
-
console.log(`✓ Injected X-Client-Config
|
|
327
|
+
console.log(`✓ Injected X-Client-Config and X-API-Key headers into extension qwen-extension.json`);
|
|
324
328
|
}
|
|
325
329
|
|
|
326
330
|
/**
|
|
@@ -343,10 +347,11 @@ function syncExtensionConfig(targetUrl, serverName) {
|
|
|
343
347
|
|
|
344
348
|
const mcpUrl = `${targetUrl}/mcp`;
|
|
345
349
|
const clientConfigHeader = getClientConfigHeaderDirect();
|
|
350
|
+
const _apiKey = getApiKeyDirect();
|
|
346
351
|
const entry = manifest.mcpServers[serverName];
|
|
347
352
|
const headersUnchanged = clientConfigHeader
|
|
348
|
-
? entry.headers && entry.headers["X-Client-Config"] === clientConfigHeader
|
|
349
|
-
: !entry.headers;
|
|
353
|
+
? entry.headers && entry.headers["X-Client-Config"] === clientConfigHeader && entry.headers["X-API-Key"] === _apiKey
|
|
354
|
+
: !entry.headers || (!_apiKey && !entry.headers["X-API-Key"]);
|
|
350
355
|
if (entry.type === "http" && entry.httpUrl === mcpUrl && headersUnchanged) {
|
|
351
356
|
console.log(`⏭️ Extension qwen-extension.json already up-to-date: ${mcpUrl}`);
|
|
352
357
|
return;
|
|
@@ -356,9 +361,12 @@ function syncExtensionConfig(targetUrl, serverName) {
|
|
|
356
361
|
entry.httpUrl = mcpUrl;
|
|
357
362
|
// 清理旧字段,避免冲突
|
|
358
363
|
delete entry.url;
|
|
359
|
-
// 透传用户本地 CODING
|
|
360
|
-
|
|
361
|
-
|
|
364
|
+
// 透传用户本地 CODING 配置和 API Key
|
|
365
|
+
const _headers = {};
|
|
366
|
+
if (clientConfigHeader) _headers["X-Client-Config"] = clientConfigHeader;
|
|
367
|
+
if (_apiKey) _headers["X-API-Key"] = _apiKey;
|
|
368
|
+
if (Object.keys(_headers).length > 0) {
|
|
369
|
+
entry.headers = _headers;
|
|
362
370
|
} else {
|
|
363
371
|
delete entry.headers;
|
|
364
372
|
}
|
|
@@ -396,10 +404,11 @@ export function setupQwenCode(deps) {
|
|
|
396
404
|
extensionInstalled = installExtensionViaCopy(deps);
|
|
397
405
|
}
|
|
398
406
|
|
|
399
|
-
// ---- 注入 X-Client-Config Header ----
|
|
407
|
+
// ---- 注入 X-Client-Config / X-API-Key Header ----
|
|
400
408
|
if (extensionInstalled) {
|
|
401
409
|
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
402
|
-
|
|
410
|
+
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
411
|
+
if (clientConfigHeader || apiKey) {
|
|
403
412
|
injectClientConfigHeader(deps.getMcpServerName(), clientConfigHeader);
|
|
404
413
|
}
|
|
405
414
|
}
|
package/scripts/lib/trae-cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from "node:fs";
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import { execSync } from "node:child_process";
|
|
17
|
-
import { loadClientCodingConfig, encodeClientConfig } from "./common.js";
|
|
17
|
+
import { loadClientCodingConfig, encodeClientConfig, getApiKey } from "./common.js";
|
|
18
18
|
|
|
19
19
|
// ==================== 路径工具 ====================
|
|
20
20
|
|
|
@@ -89,8 +89,9 @@ export function syncTraeCliYaml(deps) {
|
|
|
89
89
|
|
|
90
90
|
// 读取用户本地 CODING 配置,生成 X-Client-Config Header
|
|
91
91
|
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
92
|
-
const
|
|
93
|
-
|
|
92
|
+
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
93
|
+
const headersBlock = clientConfigHeader || apiKey
|
|
94
|
+
? `\n headers:${clientConfigHeader ? `\n X-Client-Config: ${clientConfigHeader}` : ""}${apiKey ? `\n X-API-Key: ${apiKey}` : ""}`
|
|
94
95
|
: "";
|
|
95
96
|
|
|
96
97
|
if (!fs.existsSync(yamlPath)) {
|
|
@@ -147,10 +148,12 @@ export function createTraeCliConfig(deps) {
|
|
|
147
148
|
// 读取用户本地 CODING 配置,生成 X-Client-Config Header
|
|
148
149
|
const codingConfig = loadClientCodingConfig();
|
|
149
150
|
const clientConfigHeader = encodeClientConfig(codingConfig);
|
|
151
|
+
const apiKey = getApiKey();
|
|
150
152
|
const jsonConfigObj = { type: "http", url: mcpUrl };
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
const _headers = {};
|
|
154
|
+
if (clientConfigHeader) _headers["X-Client-Config"] = clientConfigHeader;
|
|
155
|
+
if (apiKey) _headers["X-API-Key"] = apiKey;
|
|
156
|
+
if (Object.keys(_headers).length > 0) jsonConfigObj.headers = _headers;
|
|
154
157
|
const jsonConfig = JSON.stringify(jsonConfigObj);
|
|
155
158
|
|
|
156
159
|
deps.safeExecSync(`trae-cli mcp remove ${serverName} 2>/dev/null`);
|
package/scripts/lib/trae-ide.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
16
|
-
import { getClientConfigHeader } from "./common.js";
|
|
16
|
+
import { getClientConfigHeader, getApiKey } from "./common.js";
|
|
17
17
|
|
|
18
18
|
// ==================== 路径工具 ====================
|
|
19
19
|
|
|
@@ -74,7 +74,9 @@ export function syncTraeCnMcpJson(deps) {
|
|
|
74
74
|
if (!config.mcpServers) config.mcpServers = {};
|
|
75
75
|
|
|
76
76
|
const current = config.mcpServers[serverName];
|
|
77
|
-
|
|
77
|
+
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
78
|
+
const needsApiKeyUpdate = apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== apiKey);
|
|
79
|
+
if (current && current.url === mcpUrl && current.type === "http" && !needsApiKeyUpdate) {
|
|
78
80
|
console.log(`✓ Trae CN mcp.json already up-to-date: ${mcpUrl}`);
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
@@ -84,9 +86,10 @@ export function syncTraeCnMcpJson(deps) {
|
|
|
84
86
|
type: "http",
|
|
85
87
|
url: mcpUrl,
|
|
86
88
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
const headers = {};
|
|
90
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
91
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
92
|
+
if (Object.keys(headers).length > 0) serverConfig.headers = headers;
|
|
90
93
|
|
|
91
94
|
config.mcpServers[serverName] = serverConfig;
|
|
92
95
|
|
|
@@ -121,7 +124,9 @@ export function syncMcpUrl(targetUrl, serverName) {
|
|
|
121
124
|
if (!config.mcpServers) config.mcpServers = {};
|
|
122
125
|
|
|
123
126
|
const current = config.mcpServers[serverName];
|
|
124
|
-
|
|
127
|
+
const apiKey = getApiKey();
|
|
128
|
+
const needsApiKeyUpdate = apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== apiKey);
|
|
129
|
+
if (current && current.url === mcpUrl && current.type === "http" && !needsApiKeyUpdate) {
|
|
125
130
|
console.log(`⏭️ Trae CN mcp.json already up-to-date: ${mcpUrl}`);
|
|
126
131
|
return;
|
|
127
132
|
}
|
|
@@ -131,9 +136,10 @@ export function syncMcpUrl(targetUrl, serverName) {
|
|
|
131
136
|
type: "http",
|
|
132
137
|
url: mcpUrl,
|
|
133
138
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
const headers = {};
|
|
140
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
141
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
142
|
+
if (Object.keys(headers).length > 0) serverConfig.headers = headers;
|
|
137
143
|
|
|
138
144
|
config.mcpServers[serverName] = serverConfig;
|
|
139
145
|
|
package/scripts/postinstall.js
CHANGED
|
@@ -34,6 +34,7 @@ const deps = {
|
|
|
34
34
|
getMcpServerUrl: common.getMcpServerUrl,
|
|
35
35
|
getMcpServerName: common.getMcpServerName,
|
|
36
36
|
getClientConfigHeader: common.getClientConfigHeader,
|
|
37
|
+
getApiKey: common.getApiKey,
|
|
37
38
|
safeExecSync: common.safeExecSync,
|
|
38
39
|
isCommandAvailable: common.isCommandAvailable,
|
|
39
40
|
ensureDirectory: common.ensureDirectory,
|
package/scripts/update-auth.js
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
import fs from "node:fs";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import os from "node:os";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { syncMcpUrl as syncTraeIde } from "./lib/trae-ide.js";
|
|
23
|
+
import { syncMcpUrl as syncClaudeCode } from "./lib/claude-code.js";
|
|
24
|
+
import { syncMcpUrl as syncQwenCode } from "./lib/qwen-code.js";
|
|
25
|
+
import { syncMcpUrl as syncQoder } from "./lib/qoder.js";
|
|
26
|
+
import { getMcpServerUrl, getMcpServerName } from "./lib/common.js";
|
|
21
27
|
|
|
22
28
|
// ==================== 工具函数 ====================
|
|
23
29
|
|
|
@@ -86,6 +92,31 @@ function parseArgs(argv) {
|
|
|
86
92
|
return args;
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
// ==================== 同步 IDE 配置 ====================
|
|
96
|
+
|
|
97
|
+
function syncIdeConfigs() {
|
|
98
|
+
const serverUrl = getMcpServerUrl();
|
|
99
|
+
const serverName = getMcpServerName();
|
|
100
|
+
const results = [];
|
|
101
|
+
|
|
102
|
+
const syncFns = [
|
|
103
|
+
{ name: "Trae CN", fn: syncTraeIde },
|
|
104
|
+
{ name: "Claude Code", fn: syncClaudeCode },
|
|
105
|
+
{ name: "Qwen Code", fn: syncQwenCode },
|
|
106
|
+
{ name: "Qoder", fn: syncQoder },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const { name, fn } of syncFns) {
|
|
110
|
+
try {
|
|
111
|
+
fn(serverUrl, serverName);
|
|
112
|
+
results.push(` ✓ ${name}`);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
results.push(` ⏭️ ${name}: ${e.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
// ==================== 命令实现 ====================
|
|
90
121
|
|
|
91
122
|
function cmdGet() {
|
|
@@ -116,7 +147,10 @@ function cmdSet(apiKey) {
|
|
|
116
147
|
console.log(`\n✅ API Key 已保存。`);
|
|
117
148
|
console.log(` MCP_API_KEY : ${old} → ${maskKey(apiKey)}`);
|
|
118
149
|
console.log(` 配置文件 : ${getConfigFilePath()}`);
|
|
119
|
-
console.log(`\n
|
|
150
|
+
console.log(`\n 🔄 同步 IDE 配置...`);
|
|
151
|
+
const results = syncIdeConfigs();
|
|
152
|
+
for (const r of results) console.log(r);
|
|
153
|
+
console.log(`\n 提示:重启 IDE 后生效。\n`);
|
|
120
154
|
return true;
|
|
121
155
|
}
|
|
122
156
|
|
|
@@ -139,6 +173,9 @@ function cmdClear() {
|
|
|
139
173
|
console.log(`\n✅ API Key 已清除。`);
|
|
140
174
|
console.log(` MCP_API_KEY : ${old} → (未设置)`);
|
|
141
175
|
console.log(` 配置文件 : ${getConfigFilePath()}`);
|
|
176
|
+
console.log(`\n 🔄 同步 IDE 配置...`);
|
|
177
|
+
const results = syncIdeConfigs();
|
|
178
|
+
for (const r of results) console.log(r);
|
|
142
179
|
console.log(`\n 提示:服务端若已开启鉴权,清除后客户端将无法连接。\n`);
|
|
143
180
|
return true;
|
|
144
181
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: local-api-tool
|
|
3
|
-
description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool 因 LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN
|
|
3
|
+
description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool 因 LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN 场景。**仅限 10.x.x.x 网段**,172.16-31.x.x 和 192.168.x.x 服务端可直连,不要使用本技能。
|
|
4
4
|
compatibility:
|
|
5
5
|
tools:
|
|
6
6
|
- run_command
|
|
@@ -20,6 +20,19 @@ compatibility:
|
|
|
20
20
|
- API 联调测试目标为 `10.x.x.x` 网段
|
|
21
21
|
- 用户在 VPN 或内网开发环境中,本地可访问但远程服务器不可达
|
|
22
22
|
|
|
23
|
+
## ⚠️ 网段区分(重要)
|
|
24
|
+
|
|
25
|
+
**仅 `10.x.x.x` 网段需要走本地解析,其他私有网段服务端均可直连,切勿误用本技能:**
|
|
26
|
+
|
|
27
|
+
| 网段 | 是否需要本地解析 | 原因 | 处理方式 |
|
|
28
|
+
|------|:---:|------|------|
|
|
29
|
+
| `10.x.x.x` | **是** | 用户本地可达,服务端不可达 | 使用本技能在本地 curl |
|
|
30
|
+
| `172.16-31.x.x` | **否** | 服务端可直接访问 | 直接调用 api_tool,无需本技能 |
|
|
31
|
+
| `192.168.x.x` | **否** | 服务端通过弹性 IP 映射访问 | 直接调用 api_tool,无需本技能 |
|
|
32
|
+
| 公网 IP | **否** | 服务端可直接访问 | 直接调用 api_tool,无需本技能 |
|
|
33
|
+
|
|
34
|
+
**禁止对 172.16-31.x.x 和 192.168.x.x 使用本技能!** 这些地址虽然也是 RFC 1918 私有地址,但服务端网络环境可以直达,不需要在用户本地执行 curl。
|
|
35
|
+
|
|
23
36
|
## 工作流
|
|
24
37
|
|
|
25
38
|
### 流程 A:拉取 Swagger API 文档(fetch)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-browser-test
|
|
3
|
+
description: 本地浏览器测试工具,在用户本地 Chrome 中执行自动化测试。支持设计稿还原度对比(像素级评分)、功能验证(点击/输入/导航)、DOM 结构检查、JS 错误检测。通过 puppeteer-core 连接用户真实浏览器,天然继承登录态和微前端环境。
|
|
4
|
+
compatibility:
|
|
5
|
+
tools:
|
|
6
|
+
- run_command
|
|
7
|
+
- read_file
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 本地浏览器测试工具(local-browser-test)
|
|
11
|
+
|
|
12
|
+
## 触发条件
|
|
13
|
+
|
|
14
|
+
当用户说出以下关键词时启动浏览器测试:
|
|
15
|
+
- "测试一下"、"帮我验证"、"浏览器测试"
|
|
16
|
+
- "对比设计稿"、"还原度"、"看看效果"
|
|
17
|
+
- "功能测试"、"能不能正常用"
|
|
18
|
+
- "检查页面"、"页面渲染"
|
|
19
|
+
|
|
20
|
+
## 浏览器模式
|
|
21
|
+
|
|
22
|
+
支持两种模式,执行器自动检测优先使用 connect:
|
|
23
|
+
|
|
24
|
+
### connect 模式(推荐)
|
|
25
|
+
连接用户已启动的调试 Chrome,天然继承登录态/微前端环境。
|
|
26
|
+
|
|
27
|
+
用户一次性配置:
|
|
28
|
+
```bash
|
|
29
|
+
# macOS
|
|
30
|
+
open -a "Google Chrome" --args --remote-debugging-port=9222
|
|
31
|
+
# Linux
|
|
32
|
+
google-chrome --remote-debugging-port=9222
|
|
33
|
+
# Windows
|
|
34
|
+
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### launch 模式(回退)
|
|
38
|
+
启动新的 headless Chrome,需要用户提供可直接访问的 URL。
|
|
39
|
+
|
|
40
|
+
执行器会自动检测 localhost:9222 是否可用,有则 connect,无则 launch。
|
|
41
|
+
|
|
42
|
+
## 测试计划 Schema
|
|
43
|
+
|
|
44
|
+
AI 直接构造测试计划 JSON:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"planId": "<uuid>",
|
|
49
|
+
"description": "<测试描述>",
|
|
50
|
+
"steps": [ ... ],
|
|
51
|
+
"options": {
|
|
52
|
+
"viewport": { "width": 1440, "height": 900 },
|
|
53
|
+
"timeout": 120000,
|
|
54
|
+
"outputDir": "/tmp/browser-test-<planId>"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 支持的步骤类型
|
|
60
|
+
|
|
61
|
+
| type | 功能 | 参数 | 返回 |
|
|
62
|
+
|------|------|------|------|
|
|
63
|
+
| navigate | 导航到 URL | `url`, `waitUntil` | `{ url, title, loadTime }` |
|
|
64
|
+
| screenshot | 页面截图 | `fullPage`, `clip`, `filename` | `{ filepath, width, height }` |
|
|
65
|
+
| compare | 设计稿对比 | `designImagePath`, `threshold`, `regions[]` | `{ fidelity, diffPixels, diffImagePath, regions[] }` |
|
|
66
|
+
| click | 点击元素 | `selector`, `waitForNavigation` | `{ clicked, tagName }` |
|
|
67
|
+
| type | 输入文本 | `selector`, `text`, `clear` | `{ typed, value }` |
|
|
68
|
+
| waitFor | 等待条件 | `selector` 或 `url`, `timeout` | `{ matched, waited }` |
|
|
69
|
+
| evaluate | 执行 JS | `expression` | `{ result }` |
|
|
70
|
+
| checkElement | 元素断言 | `selector`, `expect: {visible, minCount, maxCount, hasText}` | `{ passed, checks, elementCount }` |
|
|
71
|
+
|
|
72
|
+
### 场景选择策略
|
|
73
|
+
|
|
74
|
+
根据上下文自动选择步骤组合:
|
|
75
|
+
|
|
76
|
+
**场景 A:有设计稿 + 功能验证(最完整)**
|
|
77
|
+
`navigate → waitFor → screenshot → compare → 功能步骤`
|
|
78
|
+
|
|
79
|
+
**场景 B:无设计稿 + 功能验证**
|
|
80
|
+
`navigate → waitFor → screenshot → evaluate(JS错误检查) → 功能步骤`
|
|
81
|
+
|
|
82
|
+
**场景 C:仅视觉对比**
|
|
83
|
+
`navigate → waitFor → screenshot → compare`
|
|
84
|
+
|
|
85
|
+
**场景 D:仅页面检查**
|
|
86
|
+
`navigate → waitFor → screenshot → evaluate(JS错误检查)`
|
|
87
|
+
|
|
88
|
+
### 构造步骤的指导原则
|
|
89
|
+
|
|
90
|
+
1. **selector 必须从用户代码中读取,不要猜测**
|
|
91
|
+
- 读代码中的 `id`、`className`、`data-testid`
|
|
92
|
+
- szcd 组件的 class 可通过 `get_component_full_profile` 获取
|
|
93
|
+
2. **API 路径从代码中的接口调用读取**
|
|
94
|
+
3. **checkElement 的 expect 从代码逻辑推断**
|
|
95
|
+
- columns 定义了 N 列 → `expect: { minCount: N }`(检查 th 数量)
|
|
96
|
+
- dataSource 有数据 → `expect: { minCount: 1 }`(检查行数)
|
|
97
|
+
4. **compare 的 regions 从设计稿分析结果或组件布局推断**
|
|
98
|
+
|
|
99
|
+
## 执行命令
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node {client_install_path}/local-browser-executor.js \
|
|
103
|
+
--plan '<测试计划JSON>' \
|
|
104
|
+
--output /tmp/browser-test-result.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
也支持从文件读取计划:
|
|
108
|
+
```bash
|
|
109
|
+
node {client_install_path}/local-browser-executor.js \
|
|
110
|
+
--plan-file /tmp/test-plan.json \
|
|
111
|
+
--output /tmp/browser-test-result.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 可选参数
|
|
115
|
+
|
|
116
|
+
- `--cdp-url http://localhost:9223`:指定非默认的 CDP 端口
|
|
117
|
+
- `--page-url http://xxx/page`:connect 模式下指定目标页面
|
|
118
|
+
|
|
119
|
+
## 依赖检查
|
|
120
|
+
|
|
121
|
+
如果执行报错依赖缺失,引导用户安装:
|
|
122
|
+
```bash
|
|
123
|
+
npm install puppeteer-core pixelmatch pngjs sharp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 结果解读
|
|
127
|
+
|
|
128
|
+
### 还原度评分
|
|
129
|
+
- fidelity >= 95%:还原度优秀
|
|
130
|
+
- fidelity 90-95%:还原度良好,有优化空间
|
|
131
|
+
- fidelity < 90%:还原度不足,需要调整
|
|
132
|
+
|
|
133
|
+
### 功能测试
|
|
134
|
+
- passed = total:全部通过
|
|
135
|
+
- 有失败步骤:根据 `step` + `error` 字段定位代码问题
|
|
136
|
+
|
|
137
|
+
### 结果文件结构
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"planId": "xxx",
|
|
141
|
+
"mode": "connect",
|
|
142
|
+
"steps": [
|
|
143
|
+
{ "index": 1, "step": "navigate", "status": "PASS", "duration": 1200, ... },
|
|
144
|
+
{ "index": 2, "step": "screenshot", "status": "PASS", "duration": 500, ... },
|
|
145
|
+
{ "index": 3, "step": "compare", "status": "PASS", "fidelity": 94.7, ... }
|
|
146
|
+
],
|
|
147
|
+
"summary": {
|
|
148
|
+
"total": 5, "passed": 4, "failed": 1,
|
|
149
|
+
"fidelity": 94.7
|
|
150
|
+
},
|
|
151
|
+
"outputDir": "/tmp/browser-test-xxx"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
@@ -19,7 +19,7 @@ compatibility:
|
|
|
19
19
|
|
|
20
20
|
**重要说明**:这是一个客户端 skill,通过 MCP 协议(SSE 或 HTTP)连接到远程 MCP 服务器。服务器由管理员维护,包含源码和数据。
|
|
21
21
|
|
|
22
|
-
**版本**:v0.
|
|
22
|
+
**版本**:v0.18.0 - 新增 MCP 鉴权支持:postinstall 自动同步 API Key 到 IDE 配置,szcd-mcp-auth 命令支持 set/clear 并自动更新所有 IDE 的 mcp.json
|
|
23
23
|
|
|
24
24
|
## 架构说明
|
|
25
25
|
|