@szc-ft/mcp-szcd-client 0.16.0 → 0.18.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/commands/szcd-mcp-auth.md +39 -0
- package/package.json +4 -2
- package/qwen-extension/commands/szcd-mcp-auth.md +39 -0
- package/qwen-extension/qwen-extension.json +1 -1
- 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 +2 -1
- package/scripts/update-auth.js +235 -0
- package/standard-skill/szcd-component-helper/SKILL.md +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 管理MCP服务器鉴权密钥(get/set/clear API Key)
|
|
3
|
+
argument-hint: <get|set|clear> [--apiKey=xxx]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
请帮我管理 szcd MCP 的鉴权配置。
|
|
7
|
+
|
|
8
|
+
**交互流程**:
|
|
9
|
+
|
|
10
|
+
1. **向用户询问要执行的操作**,提供以下选项:
|
|
11
|
+
- "查看鉴权配置" -> 执行 get
|
|
12
|
+
- "设置 API Key" -> 执行 set(需进一步追问参数)
|
|
13
|
+
- "清除 API Key" -> 执行 clear
|
|
14
|
+
|
|
15
|
+
2. **根据用户选择执行**:
|
|
16
|
+
|
|
17
|
+
- **查看鉴权配置**:直接执行 `npx szcd-mcp-auth get`
|
|
18
|
+
|
|
19
|
+
- **设置 API Key**:向用户询问 API Key(由 MCP 服务端管理员提供),收集后执行:
|
|
20
|
+
```bash
|
|
21
|
+
npx szcd-mcp-auth set --apiKey="<apiKey>"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **清除 API Key**:确认后执行 `npx szcd-mcp-auth clear`
|
|
25
|
+
|
|
26
|
+
3. **命令回退**:如果 npx 不可用,依次尝试:
|
|
27
|
+
- `node $(npm root -g)/@szc-ft/mcp-szcd-client/scripts/update-auth.js <args>`
|
|
28
|
+
- `node /scity/zengzhijie/mcp/szcd-mcp-client/scripts/update-auth.js <args>`
|
|
29
|
+
|
|
30
|
+
**配置字段说明**:
|
|
31
|
+
- `MCP_API_KEY` — MCP 服务器鉴权密钥(敏感)
|
|
32
|
+
- 设置后,客户端连接时自动通过 `X-API-Key` 请求头传递
|
|
33
|
+
- 服务端需配置相同的 `MCP_API_KEY` 环境变量才会校验
|
|
34
|
+
- 未设置时,若服务端也未开启鉴权,客户端可正常连接
|
|
35
|
+
|
|
36
|
+
配置保存在 `~/.szcd-mcp-config.json` 中,设置后重启 IDE/CLI 生效。
|
|
37
|
+
|
|
38
|
+
当前配置:
|
|
39
|
+
!`cat ~/.szcd-mcp-config.json 2>/dev/null | grep -E "MCP_API_KEY|MCP_SERVER" || echo "配置文件不存在"`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@szc-ft/mcp-szcd-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "MCP client for szcd component library - auto-configures AI coding tools with MCP server, skills, agents and commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"szcd-mcp-setup": "scripts/postinstall.js",
|
|
25
25
|
"szcd-mcp-update-url": "scripts/update-mcp-url.js",
|
|
26
26
|
"szcd-mcp-coding-config": "scripts/update-coding-config.js",
|
|
27
|
-
"szcd-mcp-api-config": "scripts/update-api-config.js"
|
|
27
|
+
"szcd-mcp-api-config": "scripts/update-api-config.js",
|
|
28
|
+
"szcd-mcp-auth": "scripts/update-auth.js"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"mcp-proxy.js",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"scripts/update-mcp-url.js",
|
|
33
34
|
"scripts/update-coding-config.js",
|
|
34
35
|
"scripts/update-api-config.js",
|
|
36
|
+
"scripts/update-auth.js",
|
|
35
37
|
"scripts/lib/",
|
|
36
38
|
"standard-skill/",
|
|
37
39
|
"agents/",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 管理MCP服务器鉴权密钥(get/set/clear API Key)
|
|
3
|
+
argument-hint: <get|set|clear> [--apiKey=xxx]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
请帮我管理 szcd MCP 的鉴权配置。
|
|
7
|
+
|
|
8
|
+
**交互流程**:
|
|
9
|
+
|
|
10
|
+
1. **向用户询问要执行的操作**,提供以下选项:
|
|
11
|
+
- "查看鉴权配置" -> 执行 get
|
|
12
|
+
- "设置 API Key" -> 执行 set(需进一步追问参数)
|
|
13
|
+
- "清除 API Key" -> 执行 clear
|
|
14
|
+
|
|
15
|
+
2. **根据用户选择执行**:
|
|
16
|
+
|
|
17
|
+
- **查看鉴权配置**:直接执行 `npx szcd-mcp-auth get`
|
|
18
|
+
|
|
19
|
+
- **设置 API Key**:向用户询问 API Key(由 MCP 服务端管理员提供),收集后执行:
|
|
20
|
+
```bash
|
|
21
|
+
npx szcd-mcp-auth set --apiKey="<apiKey>"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **清除 API Key**:确认后执行 `npx szcd-mcp-auth clear`
|
|
25
|
+
|
|
26
|
+
3. **命令回退**:如果 npx 不可用,依次尝试:
|
|
27
|
+
- `node $(npm root -g)/@szc-ft/mcp-szcd-client/scripts/update-auth.js <args>`
|
|
28
|
+
- `node /scity/zengzhijie/mcp/szcd-mcp-client/scripts/update-auth.js <args>`
|
|
29
|
+
|
|
30
|
+
**配置字段说明**:
|
|
31
|
+
- `MCP_API_KEY` — MCP 服务器鉴权密钥(敏感)
|
|
32
|
+
- 设置后,客户端连接时自动通过 `X-API-Key` 请求头传递
|
|
33
|
+
- 服务端需配置相同的 `MCP_API_KEY` 环境变量才会校验
|
|
34
|
+
- 未设置时,若服务端也未开启鉴权,客户端可正常连接
|
|
35
|
+
|
|
36
|
+
配置保存在 `~/.szcd-mcp-config.json` 中,设置后重启 IDE/CLI 生效。
|
|
37
|
+
|
|
38
|
+
当前配置:
|
|
39
|
+
!`cat ~/.szcd-mcp-config.json 2>/dev/null | grep -E "MCP_API_KEY|MCP_SERVER" || echo "配置文件不存在"`
|
|
@@ -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
|
|
|
@@ -9,7 +9,7 @@ import fs from "node:fs";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import os from "node:os";
|
|
11
11
|
import { execSync } from "node:child_process";
|
|
12
|
-
import { getClientConfigHeader as getClientConfigHeaderDirect } from "./common.js";
|
|
12
|
+
import { getClientConfigHeader as getClientConfigHeaderDirect, getApiKey as getApiKeyDirect } from "./common.js";
|
|
13
13
|
|
|
14
14
|
// ==================== 路径工具 ====================
|
|
15
15
|
|
|
@@ -55,16 +55,21 @@ function syncClaudeCodeConfig(deps) {
|
|
|
55
55
|
if (!config.mcpServers) config.mcpServers = {};
|
|
56
56
|
|
|
57
57
|
const current = config.mcpServers[serverName];
|
|
58
|
-
|
|
58
|
+
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
59
|
+
const needsApiKeyUpdate = apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== apiKey);
|
|
60
|
+
if (current && current.type === "http" && current.url === mcpUrl && !needsApiKeyUpdate) {
|
|
59
61
|
console.log(`✓ Claude Code ~/.claude.json already up-to-date: ${mcpUrl}`);
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
66
|
+
const headers = {};
|
|
67
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
68
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
64
69
|
config.mcpServers[serverName] = {
|
|
65
70
|
type: "http",
|
|
66
71
|
url: mcpUrl,
|
|
67
|
-
...(
|
|
72
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
68
73
|
};
|
|
69
74
|
|
|
70
75
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -316,16 +321,21 @@ function syncClaudeCodeConfigDirect(targetUrl, serverName) {
|
|
|
316
321
|
if (!config.mcpServers) config.mcpServers = {};
|
|
317
322
|
|
|
318
323
|
const current = config.mcpServers[serverName];
|
|
319
|
-
|
|
324
|
+
const apiKey = getApiKeyDirect();
|
|
325
|
+
const needsApiKeyUpdate = apiKey && (!current || !current.headers || current.headers["X-API-Key"] !== apiKey);
|
|
326
|
+
if (current && current.type === "http" && current.url === mcpUrl && !needsApiKeyUpdate) {
|
|
320
327
|
console.log(`⏭️ Claude Code ~/.claude.json already up-to-date: ${mcpUrl}`);
|
|
321
328
|
return;
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
const clientConfigHeader = getClientConfigHeaderDirect();
|
|
332
|
+
const headers = {};
|
|
333
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
334
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
325
335
|
config.mcpServers[serverName] = {
|
|
326
336
|
type: "http",
|
|
327
337
|
url: mcpUrl,
|
|
328
|
-
...(
|
|
338
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
329
339
|
};
|
|
330
340
|
|
|
331
341
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
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,
|
|
@@ -73,7 +74,7 @@ function createConfigTemplate() {
|
|
|
73
74
|
MCP_SERVER_URL: common.getMcpServerUrl(),
|
|
74
75
|
MCP_TIMEOUT: 30000,
|
|
75
76
|
MCP_API_KEY: "",
|
|
76
|
-
_comment: "请修改 MCP_SERVER_URL 为您的 MCP
|
|
77
|
+
_comment: "请修改 MCP_SERVER_URL 为您的 MCP 服务器地址;MCP_API_KEY 为服务端鉴权密钥(npx szcd-mcp-auth set --apiKey=xxx)",
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
common.writeFile(configPath, JSON.stringify(configContent, null, 2));
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* szcd-mcp-auth - 管理 MCP 服务器鉴权密钥
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* npx szcd-mcp-auth get 查看当前鉴权配置
|
|
8
|
+
* npx szcd-mcp-auth set --apiKey=<key> 设置 API Key
|
|
9
|
+
* npx szcd-mcp-auth clear 清除 API Key(关闭鉴权)
|
|
10
|
+
*
|
|
11
|
+
* 配置保存在 ~/.szcd-mcp-config.json 中,字段名:
|
|
12
|
+
* MCP_API_KEY — MCP 服务器鉴权密钥
|
|
13
|
+
*
|
|
14
|
+
* 客户端连接时通过 X-API-Key / Authorization: Bearer 请求头传递,
|
|
15
|
+
* 服务端校验通过后放行,否则返回 401。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
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";
|
|
27
|
+
|
|
28
|
+
// ==================== 工具函数 ====================
|
|
29
|
+
|
|
30
|
+
function getHomeDir() {
|
|
31
|
+
if (os.platform() === "win32") {
|
|
32
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
33
|
+
}
|
|
34
|
+
return process.env.HOME || os.homedir();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getConfigFilePath() {
|
|
38
|
+
return path.join(getHomeDir(), ".szcd-mcp-config.json");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadConfig() {
|
|
42
|
+
const configPath = getConfigFilePath();
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(configPath)) {
|
|
45
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Failed to read config: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function saveConfig(config) {
|
|
54
|
+
const configPath = getConfigFilePath();
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
57
|
+
return true;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`Failed to write config: ${error.message}`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function maskKey(value) {
|
|
65
|
+
if (!value) return "(未设置)";
|
|
66
|
+
if (value.length <= 8) return "***";
|
|
67
|
+
return value.substring(0, 6) + "***" + value.substring(value.length - 4);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseArgs(argv) {
|
|
71
|
+
const args = {
|
|
72
|
+
action: null,
|
|
73
|
+
apiKey: null,
|
|
74
|
+
};
|
|
75
|
+
const positional = [];
|
|
76
|
+
|
|
77
|
+
for (let i = 2; i < argv.length; i++) {
|
|
78
|
+
const arg = argv[i];
|
|
79
|
+
if (arg.startsWith("--")) {
|
|
80
|
+
const eqIdx = arg.indexOf("=");
|
|
81
|
+
if (eqIdx !== -1) {
|
|
82
|
+
const key = arg.slice(2, eqIdx);
|
|
83
|
+
const value = arg.slice(eqIdx + 1);
|
|
84
|
+
if (key === "apiKey" || key === "api-key" || key === "key") args.apiKey = value;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
positional.push(arg);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
args.action = positional[0] || "get";
|
|
92
|
+
return args;
|
|
93
|
+
}
|
|
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
|
+
|
|
120
|
+
// ==================== 命令实现 ====================
|
|
121
|
+
|
|
122
|
+
function cmdGet() {
|
|
123
|
+
const config = loadConfig();
|
|
124
|
+
|
|
125
|
+
console.log(`\nMCP 鉴权配置 (${getConfigFilePath()})\n`);
|
|
126
|
+
console.log(` MCP_API_KEY : ${maskKey(config.MCP_API_KEY)}`);
|
|
127
|
+
console.log(` 状态 : ${config.MCP_API_KEY ? "✅ 已开启鉴权" : "⚠️ 未设置(服务端无鉴权时可忽略)"}`);
|
|
128
|
+
console.log(`\n 说明:`);
|
|
129
|
+
console.log(` - 设置后,客户端连接时自动通过 X-API-Key 请求头传递`);
|
|
130
|
+
console.log(` - 服务端需配置相同的 MCP_API_KEY 环境变量才会校验`);
|
|
131
|
+
console.log(` - 用法: npx szcd-mcp-auth set --apiKey=your_api_key\n`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cmdSet(apiKey) {
|
|
135
|
+
if (!apiKey) {
|
|
136
|
+
console.error("\n❌ 缺少 --apiKey 参数。用法:");
|
|
137
|
+
console.error(" npx szcd-mcp-auth set --apiKey=your_api_key\n");
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const config = loadConfig();
|
|
142
|
+
const old = maskKey(config.MCP_API_KEY);
|
|
143
|
+
|
|
144
|
+
config.MCP_API_KEY = apiKey;
|
|
145
|
+
|
|
146
|
+
if (saveConfig(config)) {
|
|
147
|
+
console.log(`\n✅ API Key 已保存。`);
|
|
148
|
+
console.log(` MCP_API_KEY : ${old} → ${maskKey(apiKey)}`);
|
|
149
|
+
console.log(` 配置文件 : ${getConfigFilePath()}`);
|
|
150
|
+
console.log(`\n 🔄 同步 IDE 配置...`);
|
|
151
|
+
const results = syncIdeConfigs();
|
|
152
|
+
for (const r of results) console.log(r);
|
|
153
|
+
console.log(`\n 提示:重启 IDE 后生效。\n`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log("\n❌ 保存配置失败。\n");
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cmdClear() {
|
|
162
|
+
const config = loadConfig();
|
|
163
|
+
|
|
164
|
+
if (!config.MCP_API_KEY) {
|
|
165
|
+
console.log("\n⚠️ 当前未设置 API Key,无需清除。\n");
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const old = maskKey(config.MCP_API_KEY);
|
|
170
|
+
delete config.MCP_API_KEY;
|
|
171
|
+
|
|
172
|
+
if (saveConfig(config)) {
|
|
173
|
+
console.log(`\n✅ API Key 已清除。`);
|
|
174
|
+
console.log(` MCP_API_KEY : ${old} → (未设置)`);
|
|
175
|
+
console.log(` 配置文件 : ${getConfigFilePath()}`);
|
|
176
|
+
console.log(`\n 🔄 同步 IDE 配置...`);
|
|
177
|
+
const results = syncIdeConfigs();
|
|
178
|
+
for (const r of results) console.log(r);
|
|
179
|
+
console.log(`\n 提示:服务端若已开启鉴权,清除后客户端将无法连接。\n`);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log("\n❌ 清除配置失败。\n");
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ==================== 帮助信息 ====================
|
|
188
|
+
|
|
189
|
+
function printHelp() {
|
|
190
|
+
console.log(`
|
|
191
|
+
Usage: npx szcd-mcp-auth <command> [options]
|
|
192
|
+
|
|
193
|
+
Commands:
|
|
194
|
+
get 查看当前鉴权配置
|
|
195
|
+
set --apiKey=<key> 设置 MCP API Key
|
|
196
|
+
clear 清除 API Key
|
|
197
|
+
|
|
198
|
+
Options for 'set':
|
|
199
|
+
--apiKey=<string> MCP 服务器鉴权密钥(由管理员提供)
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
npx szcd-mcp-auth get
|
|
203
|
+
npx szcd-mcp-auth set --apiKey=3f4ec52f5137dbf2b458d87a28a3948ea88149b1d506e106
|
|
204
|
+
npx szcd-mcp-auth clear
|
|
205
|
+
|
|
206
|
+
Config file: ~/.szcd-mcp-config.json
|
|
207
|
+
`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ==================== 主函数 ====================
|
|
211
|
+
|
|
212
|
+
function main() {
|
|
213
|
+
const args = parseArgs(process.argv);
|
|
214
|
+
|
|
215
|
+
if (args.action === "help" || args.action === "-h" || args.action === "--help") {
|
|
216
|
+
printHelp();
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (args.action === "get") {
|
|
221
|
+
cmdGet();
|
|
222
|
+
} else if (args.action === "set") {
|
|
223
|
+
const ok = cmdSet(args.apiKey);
|
|
224
|
+
process.exit(ok ? 0 : 1);
|
|
225
|
+
} else if (args.action === "clear") {
|
|
226
|
+
const ok = cmdClear();
|
|
227
|
+
process.exit(ok ? 0 : 1);
|
|
228
|
+
} else {
|
|
229
|
+
console.error(`Unknown command: ${args.action}`);
|
|
230
|
+
printHelp();
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main();
|
|
@@ -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
|
|