@szc-ft/mcp-szcd-client 0.11.6 → 0.12.1

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 ADDED
@@ -0,0 +1,143 @@
1
+ # @szc-ft/mcp-szcd-client
2
+
3
+ szcd 组件库 MCP 客户端 — 自动配置 AI 编码工具的 MCP 服务器连接、Skills、Agents 和 Commands。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @szc-ft/mcp-szcd-client
9
+ ```
10
+
11
+ 安装后自动执行配置,支持以下 AI 编码工具:
12
+
13
+ | 工具 | 配置方式 |
14
+ |------|---------|
15
+ | Claude Code | `~/.claude.json` + `~/.claude/agents/` + `~/.claude/skills/` |
16
+ | Trae CLI | `~/.traecli/trae_cli.yaml` |
17
+ | Trae IDE | `~/.trae-cn/mcp.json` + `~/.trae-cn/agents/` |
18
+ | Qoder CLI | `~/.qoder/settings.json` + `~/.qoder/agents/` + `~/.qoder/skills/` |
19
+ | Qwen Code | `~/.qwen/extensions/szcd-component-helper/`(扩展机制) |
20
+
21
+ ## 连接方式
22
+
23
+ ### 方式1: 本地直连 (stdio)
24
+
25
+ 无需远程服务器,每次启动时本地运行 MCP 服务器进程:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "szcd-component-helper": {
31
+ "command": "npx",
32
+ "args": ["szcd-mcp-server", "--stdio"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### 方式2: Streamable HTTP 远程连接(推荐)
39
+
40
+ 连接到远程运行的 MCP 服务器,多客户端共享:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "szcd-component-helper": {
46
+ "type": "http",
47
+ "url": "http://localhost:3456/mcp"
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### 方式3: SSE 远程连接(旧客户端向后兼容)
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "szcd-component-helper": {
59
+ "type": "sse",
60
+ "url": "http://localhost:3456/sse"
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## CLI 命令
67
+
68
+ | 命令 | 功能 |
69
+ |------|------|
70
+ | `szcd-mcp-setup` | 运行安装配置(同 `npm run setup`) |
71
+ | `szcd-mcp-update-url [url]` | 更新 MCP 服务器地址并同步所有 IDE 配置 |
72
+ | `szcd-mcp-coding-config` | 配置 CODING 平台连接信息 |
73
+
74
+ ## 智能体(Agent)
75
+
76
+ 提供 `szcd-component-expert` 智能体,帮助开发者查询组件信息、匹配需求到组件、生成基于 szcd 组件库的 React 代码。
77
+
78
+ 各工具自动获取对应详细度的版本:
79
+
80
+ | 工具 | Agent 版本 | 特点 |
81
+ |------|-----------|------|
82
+ | Claude Code / Qwen | base | 基础工作流,无工具名前缀 |
83
+ | Trae | enhanced | 增强工作流,`mcp__szcd-component-helper__` 工具名前缀 |
84
+ | Qoder | full | 完整工作流 + MCP 工具表 + AskUserQuestion 交互 |
85
+
86
+ ### Agent 构建
87
+
88
+ Agent 配置采用单一源模板 + 条件标记 + 构建脚本的方案:
89
+
90
+ ```bash
91
+ npm run build:agents
92
+ ```
93
+
94
+ 修改 `agents/src/` 下的模板后运行构建,自动生成各平台适配文件。
95
+
96
+ ## Skill
97
+
98
+ 提供 `szcd-component-helper` Skill,包含 22 个 MCP 工具的使用说明。
99
+
100
+ ## 项目结构
101
+
102
+ ```
103
+ ├── agents/
104
+ │ ├── src/ # 源模板(唯一编辑入口)
105
+ │ │ ├── szcd-component-expert.md
106
+ │ │ └── tools.json
107
+ │ ├── platforms.json # 平台适配配置
108
+ │ ├── build.js # 构建脚本
109
+ │ ├── szcd-component-expert.md # [生成] Claude / Qwen
110
+ │ ├── szcd-component-expert.trae.md # [生成] Trae
111
+ │ └── szcd-component-expert.qoder.md # [生成] Qoder
112
+ ├── commands/ # 自定义命令
113
+ ├── skill/ # Skill 定义
114
+ ├── qwen-extension/ # Qwen Code 扩展包
115
+ ├── scripts/
116
+ │ ├── postinstall.js # 安装入口
117
+ │ ├── update-mcp-url.js # URL 同步工具
118
+ │ ├── update-coding-config.js # CODING 配置工具
119
+ │ └── lib/ # 各平台适配模块
120
+ │ ├── claude-code.js
121
+ │ ├── trae-cli.js
122
+ │ ├── trae-ide.js
123
+ │ ├── qoder.js
124
+ │ ├── qwen-code.js
125
+ │ └── opencode.js
126
+ └── AGENTS.md # 项目级指令
127
+ ```
128
+
129
+ ## 配置文件
130
+
131
+ MCP 服务器地址保存在 `~/.szcd-mcp-config.json`:
132
+
133
+ ```json
134
+ {
135
+ "MCP_SERVER_URL": "http://localhost:3456",
136
+ "MCP_SERVER_NAME": "szcd-component-helper",
137
+ "MCP_TIMEOUT": 30000
138
+ }
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * agents/build.js — 从源模板生成多平台智能体配置文件
5
+ *
6
+ * 用法: node agents/build.js
7
+ *
8
+ * 逻辑:
9
+ * 1. 读取 src/ 下的主模板和 tools.json
10
+ * 2. 读取 platforms.json 平台配置
11
+ * 3. 将模板整体(含 frontmatter)按 INCLUDE/EXCLUDE 过滤
12
+ * 4. 替换 {{TOOL:name}} 和 {{MCP_TOOL_TABLE}} 占位符
13
+ * 5. 写入目标文件
14
+ * 6. 同步到 mcp-server/agents/ 目录
15
+ */
16
+
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+
24
+ const AGENTS_DIR = __dirname;
25
+ const SRC_DIR = path.join(AGENTS_DIR, "src");
26
+
27
+ // ==================== 工具函数 ====================
28
+
29
+ /**
30
+ * 内容级别层级:base < enhanced < full
31
+ */
32
+ const LEVEL_HIERARCHY = ["base", "enhanced", "full"];
33
+
34
+ function getLevelIndex(level) {
35
+ const idx = LEVEL_HIERARCHY.indexOf(level);
36
+ if (idx === -1) throw new Error(`Unknown content level: ${level}`);
37
+ return idx;
38
+ }
39
+
40
+ /**
41
+ * 按内容级别过滤 INCLUDE/EXCLUDE 段
42
+ *
43
+ * 规则:
44
+ * - 无标记段:所有级别都包含
45
+ * - <!-- INCLUDE:X --> ... <!-- /INCLUDE:X -->: 仅 levelIndex >= X 的级别包含
46
+ * - <!-- EXCLUDE:X --> ... <!-- /EXCLUDE:X -->: 仅 levelIndex < X 的级别包含
47
+ *
48
+ * 同时处理 frontmatter 中的条件标记(如 <!-- EXCLUDE:enhanced -->tools: ["*"]<!-- /EXCLUDE:enhanced -->)
49
+ */
50
+ function filterByContentLevel(content, contentLevel) {
51
+ const levelIndex = getLevelIndex(contentLevel);
52
+ const lines = content.split("\n");
53
+ const result = [];
54
+ let skipStack = []; // { type: 'INCLUDE'|'EXCLUDE', levelIndex }
55
+
56
+ for (const line of lines) {
57
+ // 检查行内标记(前后在同一行)
58
+ const inlineInclude = line.match(/^(.*)<!--\s*INCLUDE:(\w+)\s*-->(.*?)<!--\s*\/INCLUDE:\2\s*-->(.*)$/);
59
+ const inlineExclude = line.match(/^(.*)<!--\s*EXCLUDE:(\w+)\s*-->(.*?)<!--\s*\/EXCLUDE:\2\s*-->(.*)$/);
60
+
61
+ if (inlineInclude) {
62
+ const [, before, lvl, inner, after] = inlineInclude;
63
+ const lvlIdx = getLevelIndex(lvl);
64
+ if (levelIndex >= lvlIdx) {
65
+ result.push(before + inner + after);
66
+ } else {
67
+ result.push(before + after);
68
+ }
69
+ continue;
70
+ }
71
+
72
+ if (inlineExclude) {
73
+ const [, before, lvl, inner, after] = inlineExclude;
74
+ const lvlIdx = getLevelIndex(lvl);
75
+ if (levelIndex < lvlIdx) {
76
+ result.push(before + inner + after);
77
+ } else {
78
+ result.push(before + after);
79
+ }
80
+ continue;
81
+ }
82
+
83
+ // 检查块级开始标记
84
+ const includeStart = line.match(/<!--\s*INCLUDE:(\w+)\s*-->/);
85
+ const excludeStart = line.match(/<!--\s*EXCLUDE:(\w+)\s*-->/);
86
+ const includeEnd = line.match(/<!--\s*\/INCLUDE:(\w+)\s*-->/);
87
+ const excludeEnd = line.match(/<!--\s*\/EXCLUDE:(\w+)\s*-->/);
88
+
89
+ if (includeStart) {
90
+ const lvlIdx = getLevelIndex(includeStart[1]);
91
+ skipStack.push({ type: "INCLUDE", levelIndex: lvlIdx });
92
+ continue;
93
+ }
94
+
95
+ if (excludeStart) {
96
+ const lvlIdx = getLevelIndex(excludeStart[1]);
97
+ skipStack.push({ type: "EXCLUDE", levelIndex: lvlIdx });
98
+ continue;
99
+ }
100
+
101
+ if (includeEnd || excludeEnd) {
102
+ skipStack.pop();
103
+ continue;
104
+ }
105
+
106
+ // 判断当前行是否应该跳过
107
+ let shouldSkip = false;
108
+ for (const rule of skipStack) {
109
+ if (rule.type === "INCLUDE" && levelIndex < rule.levelIndex) {
110
+ shouldSkip = true;
111
+ break;
112
+ }
113
+ if (rule.type === "EXCLUDE" && levelIndex >= rule.levelIndex) {
114
+ shouldSkip = true;
115
+ break;
116
+ }
117
+ }
118
+
119
+ if (!shouldSkip) {
120
+ result.push(line);
121
+ }
122
+ }
123
+
124
+ return result.join("\n");
125
+ }
126
+
127
+ /**
128
+ * 替换 {{TOOL:name}} 占位符
129
+ */
130
+ function replaceToolPlaceholders(content, toolPrefix) {
131
+ return content.replace(/\{\{TOOL:(\w+)\}\}/g, (_match, toolName) => {
132
+ return toolPrefix ? `${toolPrefix}${toolName}` : toolName;
133
+ });
134
+ }
135
+
136
+ /**
137
+ * 从 tools.json 生成 MCP 工具表格
138
+ * 按功能分组,同一 whenToUse 的工具合并在一行
139
+ */
140
+ function generateMcpToolTable(tools, toolPrefix) {
141
+ const rows = [];
142
+ const grouped = new Map();
143
+
144
+ for (const tool of tools) {
145
+ const key = tool.whenToUse;
146
+ if (!grouped.has(key)) {
147
+ grouped.set(key, []);
148
+ }
149
+ grouped.get(key).push(tool);
150
+ }
151
+
152
+ for (const [, group] of grouped) {
153
+ const names = group.map((t) => {
154
+ const fullName = toolPrefix ? `${toolPrefix}${t.name}` : t.name;
155
+ return `\`${fullName}\``;
156
+ });
157
+ rows.push(`| ${names.join(" / ")} | ${group[0].description} | ${group[0].whenToUse} |`);
158
+ }
159
+
160
+ const header = "| 工具名 | 功能 | 何时使用 |\n|--------|------|---------|";
161
+ return header + "\n" + rows.join("\n");
162
+ }
163
+
164
+ /**
165
+ * 从 frontmatter 中移除指定字段
166
+ */
167
+ function stripFrontmatterFields(content, fields) {
168
+ const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
169
+ if (!fmMatch) return content;
170
+
171
+ const prefix = fmMatch[1];
172
+ let fmBody = fmMatch[2];
173
+ const suffix = fmMatch[3];
174
+
175
+ for (const field of fields) {
176
+ // 匹配字段行(支持简单值、多行值、YAML列表等)
177
+ // 简单行:tools: ["*"]
178
+ // YAML列表:tools:\n - "*"
179
+ const simpleLineRegex = new RegExp(`^${field}:.*$\\n?`, "m");
180
+ fmBody = fmBody.replace(simpleLineRegex, "");
181
+
182
+ const yamlListRegex = new RegExp(`^${field}:\\s*\\n(?:^\\s+.*$\\n?)*`, "m");
183
+ fmBody = fmBody.replace(yamlListRegex, "");
184
+ }
185
+
186
+ // 清理空行
187
+ fmBody = fmBody.replace(/\n{2,}/g, "\n");
188
+
189
+ return prefix + fmBody + suffix + content.slice(fmMatch[0].length);
190
+ }
191
+
192
+ /**
193
+ * 清理输出内容中的多余空行和残留空行
194
+ */
195
+ function cleanupContent(content) {
196
+ // 移除连续3个以上空行 → 2个空行
197
+ let result = content.replace(/\n{4,}/g, "\n\n\n");
198
+ // 移除 frontmatter 后多余空行(--- 后最多1个空行)
199
+ result = result.replace(/(---\n)\n{2,}/g, "$1\n");
200
+ return result;
201
+ }
202
+
203
+ // ==================== 主构建逻辑 ====================
204
+
205
+ function build() {
206
+ console.log("🔧 Building agent configurations...\n");
207
+
208
+ // 1. 读取源文件
209
+ const templatePath = path.join(SRC_DIR, "szcd-component-expert.md");
210
+ const toolsPath = path.join(SRC_DIR, "tools.json");
211
+ const platformsPath = path.join(AGENTS_DIR, "platforms.json");
212
+
213
+ const templateContent = fs.readFileSync(templatePath, "utf8");
214
+ const tools = JSON.parse(fs.readFileSync(toolsPath, "utf8"));
215
+ const platformsConfig = JSON.parse(fs.readFileSync(platformsPath, "utf8"));
216
+
217
+ // 2. 按平台生成
218
+ for (const [platformName, platformConfig] of Object.entries(platformsConfig.platforms)) {
219
+ console.log(` Generating for ${platformName} (level: ${platformConfig.contentLevel})...`);
220
+
221
+ // 对整个模板内容(含 frontmatter)进行条件过滤
222
+ let filtered = filterByContentLevel(templateContent, platformConfig.contentLevel);
223
+
224
+ // 剥离 frontmatter 中指定字段(如 Qwen 不需要 tools/model/type)
225
+ if (platformConfig.stripFrontmatterFields && platformConfig.stripFrontmatterFields.length > 0) {
226
+ filtered = stripFrontmatterFields(filtered, platformConfig.stripFrontmatterFields);
227
+ }
228
+
229
+ // 替换 {{TOOL:}} 占位符
230
+ filtered = replaceToolPlaceholders(filtered, platformConfig.toolPrefix);
231
+
232
+ // 替换 {{MCP_TOOL_TABLE}}
233
+ if (filtered.includes("{{MCP_TOOL_TABLE}}")) {
234
+ const table = generateMcpToolTable(tools, platformConfig.toolPrefix);
235
+ filtered = filtered.replace("{{MCP_TOOL_TABLE}}", table);
236
+ }
237
+
238
+ // 清理
239
+ const output = cleanupContent(filtered);
240
+
241
+ // 确定输出路径
242
+ const outputDir = platformConfig.outputDir
243
+ ? path.join(AGENTS_DIR, platformConfig.outputDir)
244
+ : AGENTS_DIR;
245
+ const outputPath = path.join(outputDir, platformConfig.outputFile);
246
+
247
+ if (!fs.existsSync(outputDir)) {
248
+ fs.mkdirSync(outputDir, { recursive: true });
249
+ }
250
+
251
+ fs.writeFileSync(outputPath, output);
252
+ console.log(` ✓ Written: ${path.relative(AGENTS_DIR, outputPath)}`);
253
+ }
254
+
255
+ // 3. 同步到 mcp-server/agents/(同级 mcp/szcd-mcp-server 目录)
256
+ const mcpServerAgentsDir = path.join(AGENTS_DIR, "..", "..", "szcd-mcp-server", "agents");
257
+ if (fs.existsSync(path.dirname(mcpServerAgentsDir))) {
258
+ if (!fs.existsSync(mcpServerAgentsDir)) {
259
+ fs.mkdirSync(mcpServerAgentsDir, { recursive: true });
260
+ }
261
+
262
+ const outputFiles = [
263
+ "szcd-component-expert.md",
264
+ "szcd-component-expert.trae.md",
265
+ "szcd-component-expert.qoder.md",
266
+ ];
267
+ for (const file of outputFiles) {
268
+ const src = path.join(AGENTS_DIR, file);
269
+ const dest = path.join(mcpServerAgentsDir, file);
270
+ if (fs.existsSync(src)) {
271
+ fs.copyFileSync(src, dest);
272
+ console.log(` ✓ Synced to mcp-server: ${file}`);
273
+ }
274
+ }
275
+ }
276
+
277
+ // 4. 同步到 qwen-extension/agents/(供 Qwen Code 扩展安装使用)
278
+ const qwenExtDir = path.join(AGENTS_DIR, "..", "qwen-extension");
279
+ const qwenGeneratedAgent = path.join(AGENTS_DIR, "qwen-extension", "agents", "szcd-component-expert.md");
280
+ if (fs.existsSync(qwenGeneratedAgent)) {
281
+ const qwenExtAgentsDir = path.join(qwenExtDir, "agents");
282
+ if (!fs.existsSync(qwenExtAgentsDir)) {
283
+ fs.mkdirSync(qwenExtAgentsDir, { recursive: true });
284
+ }
285
+ fs.copyFileSync(qwenGeneratedAgent, path.join(qwenExtAgentsDir, "szcd-component-expert.md"));
286
+ console.log(` ✓ Synced to qwen-extension/agents/`);
287
+ }
288
+
289
+ // 5. 同步 SKILL.md 和 commands 到 qwen-extension/
290
+ const packageRoot = path.join(AGENTS_DIR, "..");
291
+
292
+ // 同步 SKILL.md
293
+ const skillSource = path.join(packageRoot, "skill", "SKILL.md");
294
+ const skillDest = path.join(qwenExtDir, "skills", "szcd-component-helper", "SKILL.md");
295
+ if (fs.existsSync(skillSource)) {
296
+ if (!fs.existsSync(path.dirname(skillDest))) {
297
+ fs.mkdirSync(path.dirname(skillDest), { recursive: true });
298
+ }
299
+ fs.copyFileSync(skillSource, skillDest);
300
+ console.log(` ✓ Synced to qwen-extension/skills/`);
301
+ }
302
+
303
+ // 同步 commands
304
+ const commandsSourceDir = path.join(packageRoot, "commands");
305
+ const commandsDestDir = path.join(qwenExtDir, "commands");
306
+ if (fs.existsSync(commandsSourceDir)) {
307
+ if (!fs.existsSync(commandsDestDir)) {
308
+ fs.mkdirSync(commandsDestDir, { recursive: true });
309
+ }
310
+ const cmdFiles = fs.readdirSync(commandsSourceDir).filter((f) => f.endsWith(".md"));
311
+ for (const cmdFile of cmdFiles) {
312
+ fs.copyFileSync(
313
+ path.join(commandsSourceDir, cmdFile),
314
+ path.join(commandsDestDir, cmdFile),
315
+ );
316
+ }
317
+ if (cmdFiles.length > 0) {
318
+ console.log(` ✓ Synced ${cmdFiles.length} command(s) to qwen-extension/commands/`);
319
+ }
320
+ }
321
+
322
+ // 6. 生成 qwen-extension.json(MCP 配置从 platforms.json 读取)
323
+ const qwenConfig = platformsConfig.platforms.qwen;
324
+ if (qwenConfig && qwenConfig.mcp) {
325
+ const manifestPath = path.join(qwenExtDir, "qwen-extension.json");
326
+ const pkgJsonPath = path.join(packageRoot, "package.json");
327
+ const pkgVersion = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")).version;
328
+
329
+ const manifest = {
330
+ name: platformsConfig.mcpServerName,
331
+ version: pkgVersion,
332
+ description: "szcd 组件库 MCP 助手 — 查询组件信息、匹配需求、生成代码",
333
+ mcpServers: {
334
+ [platformsConfig.mcpServerName]: {
335
+ type: qwenConfig.mcp.type,
336
+ url: qwenConfig.mcp.url,
337
+ },
338
+ },
339
+ contextFileName: "QWEN.md",
340
+ commands: "commands",
341
+ skills: "skills",
342
+ agents: "agents",
343
+ settings: [
344
+ {
345
+ name: "MCP Server URL",
346
+ description: "szcd MCP 服务器地址(默认 http://localhost:3456)",
347
+ envVar: "MCP_SERVER_URL",
348
+ sensitive: false,
349
+ },
350
+ ],
351
+ excludeTools: [],
352
+ };
353
+
354
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 4) + "\n");
355
+ console.log(` ✓ Generated qwen-extension.json (type: ${qwenConfig.mcp.type}, url: ${qwenConfig.mcp.url})`);
356
+ }
357
+
358
+ console.log("\n✅ Agent build complete!");
359
+ }
360
+
361
+ try {
362
+ build();
363
+ } catch (error) {
364
+ console.error("❌ Build failed:", error.message);
365
+ console.error(error.stack);
366
+ process.exit(1);
367
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "mcpServerName": "szcd-component-helper",
3
+ "platforms": {
4
+ "claude": {
5
+ "outputFile": "szcd-component-expert.md",
6
+ "contentLevel": "base",
7
+ "frontmatter": {
8
+ "name": "{{name}}",
9
+ "description": "{{description}}",
10
+ "tools": "[\"*\"]",
11
+ "model": "sonnet",
12
+ "type": "agent"
13
+ },
14
+ "toolPrefix": ""
15
+ },
16
+ "trae": {
17
+ "outputFile": "szcd-component-expert.trae.md",
18
+ "contentLevel": "enhanced",
19
+ "frontmatter": {
20
+ "name": "{{name}}",
21
+ "tools": "- \"*\"",
22
+ "description": "{{description}}"
23
+ },
24
+ "toolPrefix": "mcp__szcd-component-helper__"
25
+ },
26
+ "qoder": {
27
+ "outputFile": "szcd-component-expert.qoder.md",
28
+ "contentLevel": "full",
29
+ "frontmatter": {
30
+ "name": "{{name}}",
31
+ "description": "{{description}}"
32
+ },
33
+ "toolPrefix": ""
34
+ },
35
+ "qwen": {
36
+ "outputFile": "szcd-component-expert.md",
37
+ "outputDir": "qwen-extension/agents",
38
+ "contentLevel": "base",
39
+ "frontmatter": {
40
+ "name": "{{name}}",
41
+ "description": "{{description}}"
42
+ },
43
+ "stripFrontmatterFields": ["tools", "model", "type"],
44
+ "toolPrefix": "",
45
+ "mcp": {
46
+ "type": "http",
47
+ "url": "http://localhost:3456/mcp"
48
+ }
49
+ }
50
+ }
51
+ }