@szc-ft/mcp-szcd-client 0.12.2 → 0.13.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.
@@ -45,8 +45,8 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
45
45
 
46
46
  | 任务类型 | 判断条件 | 流程 |
47
47
  |---------|---------|------|
48
- | **简单查询** | 只查组件信息/API/示例,不涉及代码生成 | 快速流程(步骤1→4→6) |
49
- | **页面生成** | 需要根据需求/设计稿生成页面代码 | 完整流程(步骤1→2→3→4→5→6) |
48
+ | **简单查询** | 只查组件信息/API/示例,不涉及代码生成 | 快速流程(步骤1→4→6→7) |
49
+ | **页面生成** | 需要根据需求/设计稿生成页面代码 | 完整流程(步骤1→2→3→4→5→6→7) |
50
50
 
51
51
  ---
52
52
 
@@ -98,18 +98,26 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
98
98
 
99
99
  按以下顺序查询:
100
100
 
101
- 1. **确认组合关系**:调用 `mcp__szcd-component-helper__get_component_dependencies` 获取核心组件的依赖图,确认:
101
+ 1. **语义搜索匹配**:调用 `mcp__szcd-component-helper__search_components_semantic` 用自然语言描述搜索最匹配的组件(**优先使用**)
102
+
103
+ 2. **批量获取全景画像**:调用 `mcp__szcd-component-helper__get_component_full_profile` 获取组件全景画像。关键用法:
104
+ - `name` 支持逗号分隔批量查询(如 `"Query,TableOrList,LeftTree"`),一次调用替代多次查询
105
+ - `depth="deep"` 获取 Props 链 + 源码摘要 + 样式注入信息
106
+ - `depth="standard"` 获取完整 Props 表格 + 依赖 + 示例
107
+ - **返回值是紧凑 Markdown 格式**,直接阅读即可
108
+
109
+ 3. **确认组合关系**:调用 `mcp__szcd-component-helper__get_component_dependencies` 获取核心组件的依赖图,确认:
102
110
  - 必选组件和 hooks(如 TreeQueryTable 需要 LeftTree + Query + TableOrList + useLeftTree + useTable)
103
111
  - 插槽组件(如 CustomOption 是 LeftTree 的插槽)
104
112
  - 检查 `llmMappingHints` 是否有针对当前组件的常见错误修正
105
113
 
106
- 2. **获取组件详情**:对每个目标组件调用:
107
- - `mcp__szcd-component-helper__get_other_component` / `mcp__szcd-component-helper__get_cover_component`
108
- - `mcp__szcd-component-helper__get_accurate_component_doc`
114
+ 4. **组合最佳实践**(可选):调用 `mcp__szcd-component-helper__get_best_practices`(scenario="页面场景描述")获取页面级组件组合最佳实践
115
+
116
+ 5. **样式注入**(可选):调用 `mcp__szcd-component-helper__get_style_injection_guide` 获取样式覆盖方式和 CSS 穿透路径
109
117
 
110
- 3. **搜索使用示例**:调用 `mcp__szcd-component-helper__search_component_examples`
118
+ 6. **读取源码**(仅在 deep 模式信息不足时):调用 `mcp__szcd-component-helper__read_file` 读取具体源码文件确认行为
111
119
 
112
- 4. **如有疑问**:用 `mcp__szcd-component-helper__read_file` 读取组件源码确认
120
+ 7. **API 联调**(如需要后端接口):调用 `mcp__szcd-component-helper__api_tool`(action="fetch" 拉取文档,action="test" 联调测试)
113
121
 
114
122
 
115
123
  ### 步骤5:匹配组件并确认方案(必做)
@@ -154,14 +162,55 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
154
162
  - Hooks 按依赖关系正确引入(如 useLeftTree、useTable)
155
163
 
156
164
 
165
+ ### 步骤7:收集用户反馈(必做,质量闭环)
166
+
167
+ 代码生成完成后,**必须**向用户收集反馈,用于优化 MCP 工具行为:
168
+
169
+ 1. **询问准确率评分**:"请对本次生成的代码准确性评分(1-5 分,5 分最准确)?"
170
+ 2. **询问是否采纳**:"您是否计划采纳本次生成的代码?"
171
+ 3. **如拒绝,询问原因**:"未采纳的原因是什么?(如:组件不匹配、API 错误、缺少功能、不符合设计稿等)"
172
+ 4. **调用 `mcp__szcd-component-helper__submit_feedback` 提交反馈**:
173
+
174
+ ```
175
+ 参数示例:
176
+ {
177
+ "sessionId": "<当前会话ID或时间戳>",
178
+ "toolsUsed": ["search_components_semantic", "get_component_full_profile", ...],
179
+ "userQuery": "<用户的原始需求描述>",
180
+ "generatedCodeSummary": "<生成代码的摘要或关键组件列表>",
181
+ "accuracyRating": <1-5>,
182
+ "adopted": <true/false>,
183
+ "rejectionReason": "<拒绝原因,如采纳则为空>",
184
+ "contextSnapshot": {
185
+ "matchedComponents": ["Query", "TableOrList", "TemplateMode"],
186
+ "layoutType": "LeftRight",
187
+ "queryFields": ["name", "status", "date"]
188
+ },
189
+ "toolType": "<trae|claude|qwen|qoder|cursor|vscode>"
190
+ }
191
+ ```
192
+
193
+ **toolType 映射规则**:
194
+ - Trae CLI / Trae IDE → `"trae"`
195
+ - Claude Code / Claude Desktop → `"claude"`
196
+ - Qwen Code / 通义灵码 → `"qwen"`
197
+ - Qoder CLI → `"qoder"`
198
+ - Cursor → `"cursor"`
199
+ - VS Code + Cline / Continue → `"vscode"`
200
+ - 其他 → `"generic"`
201
+
202
+ **注意**:此步骤不阻塞用户后续操作,即使反馈提交失败也不影响已生成的代码。
203
+
204
+
157
205
  ---
158
206
 
159
207
  ## 快速流程(简单查询类任务)
160
208
 
161
- 仅执行步骤1→4→6,跳过需求分析和方案确认:
209
+ 仅执行步骤1→4→6→7,跳过需求分析和方案确认:
162
210
  1. **架构认知 + 自检** — 同步骤1
163
211
  2. **查询组件** — 按需查询目标组件(含依赖验证)
164
212
  3. **输出结果** — 直接返回组件信息/API/示例
213
+ 4. **收集反馈** — 同步骤7(如生成了代码则收集反馈)
165
214
 
166
215
  ---
167
216
 
package/mcp-proxy.js CHANGED
@@ -54,6 +54,14 @@ const SERVER_URL = process.env.MCP_SERVER_URL || fileConfig.MCP_SERVER_URL || "h
54
54
  const SERVER_TIMEOUT = parseInt(process.env.MCP_TIMEOUT || fileConfig.MCP_TIMEOUT || "120000", 10);
55
55
  const SERVER_API_KEY = process.env.MCP_API_KEY || fileConfig.MCP_API_KEY || "";
56
56
 
57
+ // 提取用户本地 CODING 配置,通过 Header 透传给服务器(服务器优先使用客户端配置)
58
+ const CLIENT_CODING_CONFIG = {};
59
+ if (fileConfig.CODING_BASE_URL) CLIENT_CODING_CONFIG.CODING_BASE_URL = fileConfig.CODING_BASE_URL;
60
+ if (fileConfig.CODING_DEFAULT_PROJECT_ID) CLIENT_CODING_CONFIG.CODING_DEFAULT_PROJECT_ID = fileConfig.CODING_DEFAULT_PROJECT_ID;
61
+ if (fileConfig.CODING_ACCOUNT) CLIENT_CODING_CONFIG.CODING_ACCOUNT = fileConfig.CODING_ACCOUNT;
62
+ if (fileConfig.CODING_COOKIES) CLIENT_CODING_CONFIG.CODING_COOKIES = fileConfig.CODING_COOKIES;
63
+ if (fileConfig.CODING_PASSWORD) CLIENT_CODING_CONFIG.CODING_PASSWORD = fileConfig.CODING_PASSWORD;
64
+
57
65
  // ==================== 日志 ====================
58
66
 
59
67
  function log(msg) {
@@ -138,6 +146,10 @@ function forwardToStreamableHttp(msg) {
138
146
  if (httpSessionId) {
139
147
  headers["Mcp-Session-Id"] = httpSessionId;
140
148
  }
149
+ // 透传用户本地 CODING 配置给服务器
150
+ if (Object.keys(CLIENT_CODING_CONFIG).length > 0) {
151
+ headers["X-Client-Config"] = Buffer.from(JSON.stringify(CLIENT_CODING_CONFIG)).toString("base64");
152
+ }
141
153
 
142
154
  const postData = JSON.stringify(msg);
143
155
 
@@ -262,6 +274,10 @@ function connectSSE() {
262
274
  headers["X-API-Key"] = SERVER_API_KEY;
263
275
  headers["Authorization"] = `Bearer ${SERVER_API_KEY}`;
264
276
  }
277
+ // 透传用户本地 CODING 配置给服务器
278
+ if (Object.keys(CLIENT_CODING_CONFIG).length > 0) {
279
+ headers["X-Client-Config"] = Buffer.from(JSON.stringify(CLIENT_CODING_CONFIG)).toString("base64");
280
+ }
265
281
 
266
282
  log(`Connecting to SSE endpoint: ${url.toString()}, headers: ${JSON.stringify(headers)}`);
267
283
 
@@ -334,6 +350,10 @@ function forwardToSSEMessageEndpoint(msg) {
334
350
  headers["X-API-Key"] = SERVER_API_KEY;
335
351
  headers["Authorization"] = `Bearer ${SERVER_API_KEY}`;
336
352
  }
353
+ // 透传用户本地 CODING 配置给服务器
354
+ if (Object.keys(CLIENT_CODING_CONFIG).length > 0) {
355
+ headers["X-Client-Config"] = Buffer.from(JSON.stringify(CLIENT_CODING_CONFIG)).toString("base64");
356
+ }
337
357
 
338
358
  const postData = JSON.stringify(msg);
339
359
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@szc-ft/mcp-szcd-client",
3
- "version": "0.12.2",
3
+ "version": "0.13.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",
@@ -78,27 +78,15 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
78
78
 
79
79
  **这一步是核心,必须实际调用 MCP 工具查询,禁止凭记忆猜测 API。**
80
80
 
81
- 使用 MCP 工具获取组件信息和 API
82
-
83
- - `get_component_library_overview` — 获取全部组件概览(含功能描述和适用场景)
84
- - `search_all_components` — 按关键词搜索组件
85
- - `get_other_component` / `get_cover_component` 获取组件详情和 Props
86
- - `get_accurate_component_doc` — 获取复合组件的透传机制
87
- - `search_component_examples` — 搜索组件使用示例
88
- - `read_file` — 读取组件源码(必要时)
89
-
90
- **映射规则**:
91
- - 页面布局 → `TemplateMode`
92
- - 查询表单 → `Query`(通过 config 配置字段)
93
- - 数据表格 → `TableOrList`
94
- - 左侧树 → `LeftTree`
95
- - 弹窗/抽屉 → `ModelOrDrawer`
96
- - 返回标题 → `TitleAndBack`
97
- - 按钮 → Cover 层 `Button`
98
- - 输入框 → Cover 层 `Input` 或 Query 的 valueType: 'input'
99
- - 下拉选择 → Cover 层 `Select` 或 Query 的 valueType: 'select'
100
- - 日期选择 → Cover 层 `DatePicker` 或 Query 的 valueType: 'datePicker'
101
- - 树选择 → Query 的 valueType: 'treeSelect'
81
+ 使用 MCP 工具获取组件信息和 API,**优先使用 Repowiki 语义搜索工具**:
82
+
83
+ - `search_components_semantic` — 基于 Repowiki 知识库的语义搜索,用自然语言描述匹配最适合的组件(**优先使用**)
84
+ - `get_component_full_profile` — 一站式获取组件全景画像。`name` 支持逗号分隔批量查询(如 `"Query,TableOrList"`),`depth="deep"` 获取 Props 链 + 源码摘要 + 样式注入,**返回值是紧凑 Markdown 格式**(**推荐首选,一次调用替代多次查询**)
85
+ - `get_component_dependencies` 查询组件依赖关系图,确认 hooks 和插槽关系
86
+ - `get_best_practices` — 获取组件使用最佳实践,指定 scenario 可获取组合最佳实践(如"左树右表页面")
87
+ - `get_style_injection_guide` — 查询组件样式注入方法,获取 CSS 覆盖路径和分配策略
88
+ - `read_file` — 读取组件源码(deep 模式信息不足时)
89
+ - `api_tool` — 拉取 API 文档(自动识别 YApi/Swagger)或联调测试接口
102
90
 
103
91
 
104
92
  ### 步骤5:匹配组件并确认方案(必做)
@@ -126,6 +114,44 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
126
114
  - "是否需要补充表单校验规则?"
127
115
  - "是否需要添加批量操作功能?"
128
116
 
117
+ ### 步骤7:收集用户反馈(必做,质量闭环)
118
+
119
+ 代码生成完成后,**必须**向用户收集反馈,用于优化 MCP 工具行为:
120
+
121
+ 1. **询问准确率评分**:"请对本次生成的代码准确性评分(1-5 分,5 分最准确)?"
122
+ 2. **询问是否采纳**:"您是否计划采纳本次生成的代码?"
123
+ 3. **如拒绝,询问原因**:"未采纳的原因是什么?(如:组件不匹配、API 错误、缺少功能、不符合设计稿等)"
124
+ 4. **调用 `submit_feedback` 提交反馈**:
125
+
126
+ ```
127
+ 参数示例:
128
+ {
129
+ "sessionId": "<当前会话ID或时间戳>",
130
+ "toolsUsed": ["search_components_semantic", "get_component_full_profile", ...],
131
+ "userQuery": "<用户的原始需求描述>",
132
+ "generatedCodeSummary": "<生成代码的摘要或关键组件列表>",
133
+ "accuracyRating": <1-5>,
134
+ "adopted": <true/false>,
135
+ "rejectionReason": "<拒绝原因,如采纳则为空>",
136
+ "contextSnapshot": {
137
+ "matchedComponents": ["Query", "TableOrList", "TemplateMode"],
138
+ "layoutType": "LeftRight",
139
+ "queryFields": ["name", "status", "date"]
140
+ },
141
+ "toolType": "<trae|claude|qwen|qoder|cursor|vscode>"
142
+ }
143
+ ```
144
+
145
+ **toolType 映射规则**:
146
+ - Trae CLI / Trae IDE → `"trae"`
147
+ - Claude Code / Claude Desktop → `"claude"`
148
+ - Qwen Code / 通义灵码 → `"qwen"`
149
+ - Qoder CLI → `"qoder"`
150
+ - Cursor → `"cursor"`
151
+ - VS Code + Cline / Continue → `"vscode"`
152
+ - 其他 → `"generic"`
153
+
154
+
129
155
  ## 典型页面模式
130
156
 
131
157
  ### 模式1: 左右布局 + 查询 + 表格(最常见)
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "szcd-component-helper",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "description": "szcd 组件库 MCP 助手 — 查询组件信息、匹配需求、生成代码",
5
5
  "mcpServers": {
6
6
  "szcd-component-helper": {
7
7
  "type": "http",
8
- "url": "http://localhost:3456/mcp"
8
+ "httpUrl": "http://localhost:3456/mcp"
9
9
  }
10
10
  },
11
11
  "contextFileName": "QWEN.md",
@@ -9,6 +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
13
 
13
14
  // ==================== 路径工具 ====================
14
15
 
@@ -59,9 +60,11 @@ function syncClaudeCodeConfig(deps) {
59
60
  return;
60
61
  }
61
62
 
63
+ const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
62
64
  config.mcpServers[serverName] = {
63
65
  type: "http",
64
66
  url: mcpUrl,
67
+ ...(clientConfigHeader ? { headers: { "X-Client-Config": clientConfigHeader } } : {}),
65
68
  };
66
69
 
67
70
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -194,38 +197,48 @@ function copyAgentToClaudeCode(deps, isProjectLevel = false) {
194
197
  // ==================== Slash Command 安装 ====================
195
198
 
196
199
  function installClaudeCodeSlashCommand(deps) {
197
- const commandSource = path.join(deps.PACKAGE_ROOT, "commands", "szcd-mcp-url.md");
198
- if (!fs.existsSync(commandSource)) {
199
- console.log("⚠️ Slash command source not found, skipping Claude Code");
200
+ const commandsSourceDir = path.join(deps.PACKAGE_ROOT, "commands");
201
+ if (!fs.existsSync(commandsSourceDir)) {
202
+ console.log("⚠️ Commands source directory not found, skipping Claude Code");
203
+ return;
204
+ }
205
+
206
+ const commandFiles = fs.readdirSync(commandsSourceDir).filter(f => f.endsWith(".md"));
207
+ if (commandFiles.length === 0) {
208
+ console.log("⚠️ No command files found, skipping Claude Code");
200
209
  return;
201
210
  }
202
211
 
203
212
  // Claude Code:项目级 .claude/commands/
204
213
  const claudeDir = path.join(deps.PROJECT_ROOT, ".claude");
205
214
  const claudeCommandsDir = path.join(claudeDir, "commands");
206
- const commandDest = path.join(claudeCommandsDir, "szcd-mcp-url.md");
207
215
 
208
216
  if (fs.existsSync(claudeDir) && !fs.statSync(claudeDir).isDirectory()) {
209
- console.log(`⚠️ Skipping Claude Code slash command: ${claudeDir} exists but is not a directory`);
217
+ console.log(`⚠️ Skipping Claude Code slash commands: ${claudeDir} exists but is not a directory`);
210
218
  return;
211
219
  }
212
220
 
213
221
  try {
214
222
  deps.ensureDirectory(claudeCommandsDir);
215
223
 
216
- if (fs.existsSync(commandDest)) {
217
- const existingContent = fs.readFileSync(commandDest, "utf8").trim();
218
- const sourceContent = fs.readFileSync(commandSource, "utf8").trim();
219
- if (existingContent !== sourceContent) {
220
- console.log(`⏭️ Skipping Claude Code slash command: ${commandDest} already exists (user modified)`);
221
- return;
224
+ for (const file of commandFiles) {
225
+ const commandSource = path.join(commandsSourceDir, file);
226
+ const commandDest = path.join(claudeCommandsDir, file);
227
+
228
+ if (fs.existsSync(commandDest)) {
229
+ const existingContent = fs.readFileSync(commandDest, "utf8").trim();
230
+ const sourceContent = fs.readFileSync(commandSource, "utf8").trim();
231
+ if (existingContent !== sourceContent) {
232
+ console.log(`⏭️ Skipping Claude Code command ${file}: already exists (user modified)`);
233
+ continue;
234
+ }
222
235
  }
223
- }
224
236
 
225
- deps.copyFile(commandSource, commandDest);
226
- console.log(`✓ Installed Claude Code project slash command: /szcd-mcp-url`);
237
+ deps.copyFile(commandSource, commandDest);
238
+ console.log(`✓ Installed Claude Code slash command: /${file.replace(".md", "")}`);
239
+ }
227
240
  } catch (error) {
228
- console.log(`⚠️ Failed to install Claude Code slash command: ${error.message}`);
241
+ console.log(`⚠️ Failed to install Claude Code slash commands: ${error.message}`);
229
242
  }
230
243
  }
231
244
 
@@ -304,9 +317,11 @@ function syncClaudeCodeConfigDirect(targetUrl, serverName) {
304
317
  return;
305
318
  }
306
319
 
320
+ const clientConfigHeader = getClientConfigHeaderDirect();
307
321
  config.mcpServers[serverName] = {
308
322
  type: "http",
309
323
  url: mcpUrl,
324
+ ...(clientConfigHeader ? { headers: { "X-Client-Config": clientConfigHeader } } : {}),
310
325
  };
311
326
 
312
327
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -159,3 +159,36 @@ export function getMcpServerName() {
159
159
  const config = loadExistingConfig();
160
160
  return config.MCP_SERVER_NAME || DEFAULT_MCP_SERVER_NAME;
161
161
  }
162
+
163
+ // ==================== 客户端 CODING 配置透传 ====================
164
+
165
+ /**
166
+ * 读取用户本地 ~/.szcd-mcp-config.json 中的 CODING 配置
167
+ * 用于通过 HTTP Header (X-Client-Config) 透传给 MCP 服务器
168
+ */
169
+ export function loadClientCodingConfig() {
170
+ const config = loadExistingConfig();
171
+ const codingConfig = {};
172
+ if (config.CODING_BASE_URL) codingConfig.CODING_BASE_URL = config.CODING_BASE_URL;
173
+ if (config.CODING_DEFAULT_PROJECT_ID) codingConfig.CODING_DEFAULT_PROJECT_ID = config.CODING_DEFAULT_PROJECT_ID;
174
+ if (config.CODING_ACCOUNT) codingConfig.CODING_ACCOUNT = config.CODING_ACCOUNT;
175
+ if (config.CODING_COOKIES) codingConfig.CODING_COOKIES = config.CODING_COOKIES;
176
+ if (config.CODING_PASSWORD) codingConfig.CODING_PASSWORD = config.CODING_PASSWORD;
177
+ return Object.keys(codingConfig).length > 0 ? codingConfig : null;
178
+ }
179
+
180
+ /**
181
+ * 将 CODING 配置编码为 Base64,用于 X-Client-Config Header
182
+ */
183
+ export function encodeClientConfig(config) {
184
+ if (!config) return null;
185
+ return Buffer.from(JSON.stringify(config)).toString("base64");
186
+ }
187
+
188
+ /**
189
+ * 获取 X-Client-Config Header 值(便捷函数)
190
+ * 返回 Base64 编码的字符串,或 null(无 CODING 配置时)
191
+ */
192
+ export function getClientConfigHeader() {
193
+ return encodeClientConfig(loadClientCodingConfig());
194
+ }
@@ -29,6 +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
33
 
33
34
  // ==================== 路径工具 ====================
34
35
 
@@ -148,9 +149,11 @@ function syncQoderSettings(deps) {
148
149
 
149
150
  if (!settings.mcpServers) settings.mcpServers = {};
150
151
 
152
+ const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
151
153
  const desiredConfig = {
152
154
  type: "http",
153
155
  url: mcpUrl,
156
+ ...(clientConfigHeader ? { headers: { "X-Client-Config": clientConfigHeader } } : {}),
154
157
  };
155
158
 
156
159
  const current = settings.mcpServers[serverName];
@@ -358,9 +361,11 @@ function setupQoderProjectConfig(deps) {
358
361
  let settings = readJsonFile(projectSettingsPath);
359
362
  if (!settings.mcpServers) settings.mcpServers = {};
360
363
 
364
+ const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
361
365
  settings.mcpServers[serverName] = {
362
366
  type: "http",
363
367
  url: mcpUrl,
368
+ ...(clientConfigHeader ? { headers: { "X-Client-Config": clientConfigHeader } } : {}),
364
369
  };
365
370
 
366
371
  writeJsonFile(projectSettingsPath, settings);
@@ -440,9 +445,11 @@ function syncQoderSettingsDirect(targetUrl, serverName) {
440
445
  return;
441
446
  }
442
447
 
448
+ const clientConfigHeader = _getClientConfigHeader();
443
449
  settings.mcpServers[serverName] = {
444
450
  type: "http",
445
451
  url: mcpUrl,
452
+ ...(clientConfigHeader ? { headers: { "X-Client-Config": clientConfigHeader } } : {}),
446
453
  };
447
454
 
448
455
  writeJsonFile(settingsPath, settings);
@@ -23,6 +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
27
 
27
28
  // ==================== 路径工具 ====================
28
29
 
@@ -299,6 +300,29 @@ function cleanupSettingsMcpEntry(serverName) {
299
300
  console.log(`✓ Cleaned up mcpServers in ~/.qwen/settings.json (extension mode)`);
300
301
  }
301
302
 
303
+ /**
304
+ * 向已安装扩展的 qwen-extension.json 注入 X-Client-Config Header
305
+ * 仅在 postinstall 时调用(setupQwenCode),syncMcpUrl 路径由 syncExtensionConfig 处理
306
+ */
307
+ function injectClientConfigHeader(serverName, clientConfigHeader) {
308
+ const extDir = path.join(getQwenCodeExtensionsDirectory(), serverName);
309
+ const manifestPath = path.join(extDir, "qwen-extension.json");
310
+
311
+ if (!fs.existsSync(manifestPath)) return;
312
+
313
+ const manifest = readJsonFile(manifestPath);
314
+ if (!manifest.mcpServers || !manifest.mcpServers[serverName]) return;
315
+
316
+ const entry = manifest.mcpServers[serverName];
317
+ if (entry.headers && entry.headers["X-Client-Config"] === clientConfigHeader) {
318
+ return; // 已是最新
319
+ }
320
+
321
+ entry.headers = { "X-Client-Config": clientConfigHeader };
322
+ writeJsonFile(manifestPath, manifest);
323
+ console.log(`✓ Injected X-Client-Config header into extension qwen-extension.json`);
324
+ }
325
+
302
326
  /**
303
327
  * 更新已安装扩展目录中的 qwen-extension.json
304
328
  */
@@ -318,14 +342,26 @@ function syncExtensionConfig(targetUrl, serverName) {
318
342
  }
319
343
 
320
344
  const mcpUrl = `${targetUrl}/mcp`;
345
+ const clientConfigHeader = getClientConfigHeaderDirect();
321
346
  const entry = manifest.mcpServers[serverName];
322
- if (entry.type === "http" && entry.url === mcpUrl) {
347
+ const headersUnchanged = clientConfigHeader
348
+ ? entry.headers && entry.headers["X-Client-Config"] === clientConfigHeader
349
+ : !entry.headers;
350
+ if (entry.type === "http" && entry.httpUrl === mcpUrl && headersUnchanged) {
323
351
  console.log(`⏭️ Extension qwen-extension.json already up-to-date: ${mcpUrl}`);
324
352
  return;
325
353
  }
326
354
 
327
355
  entry.type = "http";
328
- entry.url = mcpUrl;
356
+ entry.httpUrl = mcpUrl;
357
+ // 清理旧字段,避免冲突
358
+ delete entry.url;
359
+ // 透传用户本地 CODING 配置
360
+ if (clientConfigHeader) {
361
+ entry.headers = { "X-Client-Config": clientConfigHeader };
362
+ } else {
363
+ delete entry.headers;
364
+ }
329
365
  writeJsonFile(manifestPath, manifest);
330
366
  console.log(`✓ Updated extension qwen-extension.json: ${mcpUrl}`);
331
367
  }
@@ -360,6 +396,14 @@ export function setupQwenCode(deps) {
360
396
  extensionInstalled = installExtensionViaCopy(deps);
361
397
  }
362
398
 
399
+ // ---- 注入 X-Client-Config Header ----
400
+ if (extensionInstalled) {
401
+ const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
402
+ if (clientConfigHeader) {
403
+ injectClientConfigHeader(deps.getMcpServerName(), clientConfigHeader);
404
+ }
405
+ }
406
+
363
407
  // ---- 结果 ----
364
408
  if (extensionInstalled) {
365
409
  console.log(`\n✅ Qwen Code extension installed successfully!`);
@@ -73,6 +73,12 @@ function parseYamlMcpServers(content) {
73
73
  return { servers, lines };
74
74
  }
75
75
 
76
+ // ==================== 客户端 CODING 配置(使用 common.js 公共函数)====================
77
+
78
+ // loadClientCodingConfig / encodeClientConfig 已在 common.js 中定义,
79
+ // 通过 deps 参数传入(deps.getClientConfigHeader())
80
+ // 此处仅保留兼容旧代码的本地引用
81
+
76
82
  // ==================== YAML 文件同步 ====================
77
83
 
78
84
  export function syncTraeCliYaml(deps) {
@@ -80,10 +86,16 @@ export function syncTraeCliYaml(deps) {
80
86
  const mcpUrl = `${deps.getMcpServerUrl()}/mcp`;
81
87
  const serverName = deps.getMcpServerName();
82
88
 
89
+ // 读取用户本地 CODING 配置,生成 X-Client-Config Header
90
+ const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
91
+ const headersBlock = clientConfigHeader
92
+ ? `\n headers:\n X-Client-Config: ${clientConfigHeader}`
93
+ : "";
94
+
83
95
  if (!fs.existsSync(yamlPath)) {
84
96
  const dir = path.dirname(yamlPath);
85
97
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
86
- const content = `mcp_servers:\n - name: ${serverName}\n url: ${mcpUrl}\n type: http\n`;
98
+ const content = `mcp_servers:\n - name: ${serverName}\n url: ${mcpUrl}\n type: http${headersBlock}\n`;
87
99
  fs.writeFileSync(yamlPath, content);
88
100
  console.log(`✓ Created Trae CLI YAML: ${yamlPath}`);
89
101
  return;
@@ -93,7 +105,7 @@ export function syncTraeCliYaml(deps) {
93
105
  const { servers } = parseYamlMcpServers(content);
94
106
 
95
107
  const existing = servers.find(s => s.name === serverName);
96
- if (existing && existing.url === mcpUrl) {
108
+ if (existing && existing.url === mcpUrl && existing.type === "http") {
97
109
  console.log(`✓ Trae CLI YAML already up-to-date: ${mcpUrl}`);
98
110
  return;
99
111
  }
@@ -101,13 +113,14 @@ export function syncTraeCliYaml(deps) {
101
113
  if (existing) {
102
114
  const newRaw = existing.raw.map(l =>
103
115
  l.replace(/^(\s+url\s*:\s*).*/, `$1${mcpUrl}`)
116
+ .replace(/^(\s+type\s*:\s*).*/, `$1http`)
104
117
  );
105
118
  const oldBlock = existing.raw.join("\n");
106
119
  const newBlock = newRaw.join("\n");
107
120
  content = content.replace(oldBlock, newBlock);
108
- console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${mcpUrl}`);
121
+ console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${mcpUrl}, type → http`);
109
122
  } else {
110
- const entry = ` - name: ${serverName}\n url: ${mcpUrl}\n type: http\n`;
123
+ const entry = ` - name: ${serverName}\n url: ${mcpUrl}\n type: http${headersBlock}\n`;
111
124
  if (/^mcp_servers\s*:\s*\[\]\s*$/m.test(content)) {
112
125
  content = content.replace(/^mcp_servers\s*:\s*\[\]\s*$/m, `mcp_servers:\n${entry}`);
113
126
  } else if (/^mcp_servers\s*:\s*$/m.test(content)) {
@@ -129,7 +142,16 @@ export function createTraeCliConfig(deps) {
129
142
  if (deps.isCommandAvailable("trae-cli")) {
130
143
  const mcpUrl = `${deps.getMcpServerUrl()}/mcp`;
131
144
  const serverName = deps.getMcpServerName();
132
- const jsonConfig = `{"type":"http","url":"${mcpUrl}"}`;
145
+
146
+ // 读取用户本地 CODING 配置,生成 X-Client-Config Header
147
+ const codingConfig = loadClientCodingConfig();
148
+ const clientConfigHeader = encodeClientConfig(codingConfig);
149
+ const jsonConfigObj = { type: "http", url: mcpUrl };
150
+ if (clientConfigHeader) {
151
+ jsonConfigObj.headers = { "X-Client-Config": clientConfigHeader };
152
+ }
153
+ const jsonConfig = JSON.stringify(jsonConfigObj);
154
+
133
155
  deps.safeExecSync(`trae-cli mcp remove ${serverName} 2>/dev/null`);
134
156
  const result = deps.safeExecSync(`trae-cli mcp add-json ${serverName} '${jsonConfig}'`);
135
157
  if (result === true || result === "already_exists") {
@@ -176,17 +198,25 @@ export function getTraeCliCommandsDirectory() {
176
198
  }
177
199
 
178
200
  export function installTraeCliSlashCommand(deps) {
179
- const commandSource = path.join(deps.PACKAGE_ROOT, "commands", "szcd-mcp-url.md");
180
- if (!fs.existsSync(commandSource)) {
181
- console.log("⚠️ Slash command source not found, skipping");
201
+ const commandsSourceDir = path.join(deps.PACKAGE_ROOT, "commands");
202
+ if (!fs.existsSync(commandsSourceDir)) {
203
+ console.log("⚠️ Commands source directory not found, skipping");
204
+ return;
205
+ }
206
+
207
+ const commandFiles = fs.readdirSync(commandsSourceDir).filter(f => f.endsWith(".md"));
208
+ if (commandFiles.length === 0) {
209
+ console.log("⚠️ No command files found, skipping");
182
210
  return;
183
211
  }
184
212
 
185
213
  const traeCliCommandsDir = getTraeCliCommandsDirectory();
186
214
  try {
187
215
  deps.ensureDirectory(traeCliCommandsDir);
188
- deps.copyFile(commandSource, path.join(traeCliCommandsDir, "szcd-mcp-url.md"));
189
- console.log(`✓ Installed Trae CLI slash command: /szcd-mcp-url`);
216
+ for (const file of commandFiles) {
217
+ deps.copyFile(path.join(commandsSourceDir, file), path.join(traeCliCommandsDir, file));
218
+ console.log(`✓ Installed Trae CLI slash command: /${file.replace(".md", "")}`);
219
+ }
190
220
  } catch (error) {
191
221
  console.log(`⚠️ Failed to install Trae CLI slash command: ${error.message}`);
192
222
  }
@@ -266,7 +296,7 @@ function syncTraeCliYamlDirect(targetUrl, serverName) {
266
296
  const { servers } = parseYamlMcpServers(content);
267
297
 
268
298
  const existing = servers.find(s => s.name === serverName);
269
- if (existing && existing.url === mcpUrl) {
299
+ if (existing && existing.url === mcpUrl && existing.type === "http") {
270
300
  console.log(`⏭️ Trae CLI YAML already up-to-date: ${mcpUrl}`);
271
301
  return;
272
302
  }
@@ -274,11 +304,12 @@ function syncTraeCliYamlDirect(targetUrl, serverName) {
274
304
  if (existing) {
275
305
  const newRaw = existing.raw.map(l =>
276
306
  l.replace(/^(\s+url\s*:\s*).*/, `$1${mcpUrl}`)
307
+ .replace(/^(\s+type\s*:\s*).*/, `$1http`)
277
308
  );
278
309
  const oldBlock = existing.raw.join("\n");
279
310
  const newBlock = newRaw.join("\n");
280
311
  content = content.replace(oldBlock, newBlock);
281
- console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${mcpUrl}`);
312
+ console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${mcpUrl}, type → http`);
282
313
  } else {
283
314
  const entry = ` - name: ${serverName}\n url: ${mcpUrl}\n type: http\n`;
284
315
  if (/^mcp_servers\s*:\s*\[\]\s*$/m.test(content)) {