@szc-ft/mcp-szcd-client 0.21.0 → 0.23.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/build.js +152 -130
- package/agents/opencode-extension/agents/szcd-component-expert.md +138 -12
- package/agents/platforms.json +17 -7
- package/agents/qwen-extension/agents/szcd-component-expert.md +137 -12
- package/agents/src/szcd-component-expert.md +211 -6
- package/agents/src/tools.json +10 -5
- package/agents/szcd-component-expert.md +139 -14
- package/agents/szcd-component-expert.qoder.md +227 -15
- package/agents/szcd-component-expert.trae.md +217 -13
- package/opencode-extension/agents/szcd-component-expert.md +138 -12
- package/opencode-extension/commands/szcd-mcp-api-config.md +48 -0
- package/opencode-extension/commands/szcd-mcp-auth.md +39 -0
- package/opencode-extension/commands/szcd-mcp-browser-test.md +57 -0
- package/opencode-extension/commands/szcd-mcp-coding-config.md +40 -0
- package/opencode-extension/commands/szcd-mcp-feedback.md +42 -0
- package/opencode-extension/commands/szcd-mcp-url.md +37 -0
- package/opencode-extension/opencode.json +15 -0
- package/opencode-extension/skills/local-api-tool/SKILL.md +246 -0
- package/opencode-extension/skills/local-browser-test/SKILL.md +249 -0
- package/opencode-extension/skills/szcd-component-helper/SKILL.md +523 -0
- package/opencode-extension/skills/szcd-design-to-code/SKILL.md +177 -0
- package/package.json +3 -2
- package/qwen-extension/QWEN.md +29 -8
- package/qwen-extension/agents/szcd-component-expert.md +137 -12
- package/qwen-extension/qwen-extension.json +6 -1
- package/qwen-extension/skills/szcd-component-helper/SKILL.md +81 -1
- package/qwen-extension/skills/szcd-design-to-code/SKILL.md +177 -0
- package/scripts/lib/claude-code.js +45 -1
- package/scripts/lib/common.js +17 -0
- package/scripts/lib/opencode.js +108 -12
- package/scripts/lib/qoder.js +42 -1
- package/scripts/lib/trae-cli.js +39 -1
- package/scripts/lib/trae-ide.js +40 -1
- package/scripts/postinstall.js +28 -0
- package/standard-skill/szcd-component-helper/SKILL.md +81 -1
- package/standard-skill/szcd-design-to-code/SKILL.md +177 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: szcd-design-to-code
|
|
3
|
+
description: |
|
|
4
|
+
根据设计稿(Sketch 文件或图片)或页面需求描述,生成基于 @szc-ft/szcd 组件库的 React 页面代码。
|
|
5
|
+
工作流在主智能体上下文中直接执行,调用 szcd-component-helper 和 sketch-mcp-server MCP 工具完成设计稿解析、组件映射和代码生成。
|
|
6
|
+
|
|
7
|
+
触发场景:
|
|
8
|
+
- 用户提供 .sketch 文件路径,要求生成页面代码
|
|
9
|
+
- 用户提供设计稿截图,要求生成页面代码
|
|
10
|
+
- 用户描述页面需求(如"做一个左侧树+右侧表格的页面"),要求生成代码
|
|
11
|
+
- 用户提到"设计稿转代码"、"根据设计稿生成页面"、"需求转组件"等关键词
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# szcd-design-to-code: 设计稿转 szcd 页面代码
|
|
15
|
+
|
|
16
|
+
## 角色定位
|
|
17
|
+
|
|
18
|
+
你是 szcd 组件库(@szc-ft/szcd)的代码生成专家。在主智能体上下文中直接执行以下工作流,调用已连接的 MCP 工具完成从设计稿到可运行代码的全流程。
|
|
19
|
+
|
|
20
|
+
szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用7层分层架构:
|
|
21
|
+
Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages → 复合组件 → 页面模板
|
|
22
|
+
|
|
23
|
+
**关键原则**:能用模板就用模板,能用复合组件就用复合组件,能用 Wrapper 就不用 Cover。
|
|
24
|
+
|
|
25
|
+
## ⚠️ 强制执行规则
|
|
26
|
+
|
|
27
|
+
- 禁止跳过架构认知直接生成代码
|
|
28
|
+
- 禁止不查询组件 API 就凭记忆/猜测写代码
|
|
29
|
+
- 禁止跳过方案确认直接进入代码生成
|
|
30
|
+
- 禁止使用 antd 原生组件而不优先考虑 szcd 复合组件
|
|
31
|
+
- 禁止忽视 llmMappingHints 中的常见错误提示
|
|
32
|
+
|
|
33
|
+
## 工作流
|
|
34
|
+
|
|
35
|
+
### 步骤1:架构认知(必做)
|
|
36
|
+
|
|
37
|
+
调用 `mcp__szcd-component-helper__get_architecture_overview`(detail="summary")获取:
|
|
38
|
+
- `templatePatterns`:4种模板模式(TreeQueryTable/QueryTabsTables/LeftRight/UpDown)
|
|
39
|
+
- `llmMappingHints.commonMistakes`:LLM 常见映射错误
|
|
40
|
+
- `recommendedUsageOrder`:推荐使用顺序
|
|
41
|
+
|
|
42
|
+
根据返回的 `templatePatterns` 初步判断用户需求匹配哪个模板。
|
|
43
|
+
|
|
44
|
+
### 步骤2:理解需求(必做)
|
|
45
|
+
|
|
46
|
+
分析用户页面,梳理:
|
|
47
|
+
1. **页面布局类型**:左右 / 上下 / 表单页 / 详情页 / 步骤向导
|
|
48
|
+
2. **查询条件**:搜索字段及类型
|
|
49
|
+
3. **操作按钮**:新增/编辑/删除/批量操作
|
|
50
|
+
4. **表格列**:列名、数据类型
|
|
51
|
+
5. **左侧树**:是否需要
|
|
52
|
+
6. **弹窗/抽屉**:交互方式
|
|
53
|
+
|
|
54
|
+
信息不足时主动追问,不要自行假设。
|
|
55
|
+
|
|
56
|
+
### 步骤2.5:Sketch 文件解析(有 .sketch 文件时执行)
|
|
57
|
+
|
|
58
|
+
**核心原则**:写代码之前必须拿到所有 `text` 和 `rectangle` 节点的完整样式 Token,禁止凭节点名猜测颜色/字号/背景色。一次 `getMultipleNodeInfo(50-100 个节点)` 比逐个 `getNodeInfo` 快 10 倍以上,且不会遗漏。
|
|
59
|
+
|
|
60
|
+
**工具调用顺序(深度探查,像素级还原)**:
|
|
61
|
+
|
|
62
|
+
1. `mcp__sketch-mcp-server__loadSketchByPath` — 加载文件
|
|
63
|
+
2. `mcp__sketch-mcp-server__listPages` — 获取页面列表
|
|
64
|
+
3. `mcp__sketch-mcp-server__listNodesByPage`(type="artboard")— 获取画板列表,确定目标画板
|
|
65
|
+
4. `mcp__sketch-mcp-server__getPageStructure`(pageId)**或** `getDocumentStructure`(includeDetails=true)— 拿到完整节点树 + 所有节点 ID(含初始样式)
|
|
66
|
+
5. `mcp__sketch-mcp-server__getNodesSummary`(pageId, groupBy="type")— 按类型分组统计,识别组件模式
|
|
67
|
+
- `group(rectangle + text)` = 标签组件
|
|
68
|
+
- `group(text + text + rectangle...)` = 表单项
|
|
69
|
+
- 统计各类型数量,决定下一批查询范围
|
|
70
|
+
6. `mcp__sketch-mcp-server__getMultipleNodeInfo`([id1, id2, ...])— **批量查询所有 type=text 和 type=rectangle 节点**,每次 ≤ 100 个
|
|
71
|
+
- 关键字段:`style.fills[0].color.hex`(背景色)、`text.color`(文字色)、`style.fontSize`、`style.fontWeight`
|
|
72
|
+
- 不要逐个 `getNodeInfo`,太慢且容易漏
|
|
73
|
+
|
|
74
|
+
**⚠️ 与旧策略的差异**:
|
|
75
|
+
- 旧策略:maxDepth=2 拿布局,样式靠组件默认
|
|
76
|
+
- 新策略:includeDetails=true + 批量 `getMultipleNodeInfo` 拿所有 text/rectangle 完整样式 Token
|
|
77
|
+
- 适用场景:需要像素级还原设计稿(颜色、字号、标签背景色等)
|
|
78
|
+
- 代价:token 消耗约 10 倍(旧策略的几 KB → 新策略的几十到几百 KB,取决于节点数)
|
|
79
|
+
|
|
80
|
+
**Sketch 结构 → 组件映射规则**:
|
|
81
|
+
|
|
82
|
+
| Sketch 特征 | 推断组件 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| 左右分区布局 | `TemplateMode(templateType="LeftRight")` |
|
|
85
|
+
| 上下分区布局 | `TemplateMode(templateType="TopBottom")` |
|
|
86
|
+
| 左侧窄区域 + 树形图层 | `LeftTree` |
|
|
87
|
+
| 顶部输入框/下拉框/日期选择器 | `Query` |
|
|
88
|
+
| 中间表头 + 数据行图层 | `TableOrList` |
|
|
89
|
+
| 弹窗/抽屉图层 | `ModelOrDrawer` |
|
|
90
|
+
| 页面标题 + 返回箭头 | `TitleAndBack` |
|
|
91
|
+
| 详情展示区域(只读字段) | `FormItemOrDetailItem(type="detail")` |
|
|
92
|
+
| 卡片列表(非表格式数据卡片) | `TableOrList(componentType="ProList")` |
|
|
93
|
+
| 可编辑表格 | `TableOrList(componentType="EditableProTable")` |
|
|
94
|
+
| 折线趋势图 | `Line` |
|
|
95
|
+
| 柱状对比图 | `Bar` |
|
|
96
|
+
| 饼图/环形图 | `Pie` |
|
|
97
|
+
|
|
98
|
+
推理出组件候选列表后,立即进入步骤4批量获取组件 API。
|
|
99
|
+
|
|
100
|
+
### 步骤3:分析设计稿图片(有图片时执行)
|
|
101
|
+
|
|
102
|
+
- 主智能体已提供图片描述时,直接使用,跳过此步
|
|
103
|
+
- 否则调用 `mcp__szcd-component-helper__analyze_design_image` 分析
|
|
104
|
+
- 获得描述后,结合步骤1的 `templatePatterns` 推理组件候选列表
|
|
105
|
+
- 立即调用 `mcp__szcd-component-helper__get_component_full_profile`(批量,depth="gencode")
|
|
106
|
+
|
|
107
|
+
### 步骤4:批量获取组件 API(必做)
|
|
108
|
+
|
|
109
|
+
调用 `mcp__szcd-component-helper__get_component_full_profile`:
|
|
110
|
+
- `name` 用逗号分隔一次性查询所有候选组件,如 `"Query,TableOrList,LeftTree,TemplateMode"`
|
|
111
|
+
- `depth="gencode"`(比 deep 小 30-50%,生成代码场景首选)
|
|
112
|
+
- 以下复合组件 **必须用 gencode 或 deep**:TableOrList, ModelOrDrawer, TemplateMode, TitleAndBack, Query, LeftTree, FormItemOrDetailItem, TableListCardSwitch
|
|
113
|
+
|
|
114
|
+
确认依赖关系(如 TreeQueryTable 需要 useLeftTree + useTable)。
|
|
115
|
+
|
|
116
|
+
需要样式注入时追加 `mcp__szcd-component-helper__get_style_injection_guide`。
|
|
117
|
+
|
|
118
|
+
### 步骤5:确认组件方案(必做)
|
|
119
|
+
|
|
120
|
+
输出以下内容供用户确认,**等用户确认后才能生成代码**:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
## 组件方案
|
|
124
|
+
|
|
125
|
+
### 页面结构
|
|
126
|
+
[布局树形图,标注模板类型]
|
|
127
|
+
|
|
128
|
+
### 选用组件
|
|
129
|
+
| 设计元素 | 选用组件 | 来源层级 | 选用理由 |
|
|
130
|
+
|---------|---------|---------|---------|
|
|
131
|
+
|
|
132
|
+
### 依赖关系
|
|
133
|
+
| 组件 | 依赖的 szcd 组件 | 依赖的 hooks |
|
|
134
|
+
|------|-----------------|-------------|
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 步骤6:生成代码(必做)
|
|
138
|
+
|
|
139
|
+
遵循规范:
|
|
140
|
+
- React 18 + TypeScript
|
|
141
|
+
- 从 `@szc-ft/szcd` 导入组件
|
|
142
|
+
- antd 5.27 API 用法
|
|
143
|
+
- 4 空格缩进,双引号,末尾逗号
|
|
144
|
+
- Hooks 按依赖关系正确引入(useLeftTree、useTable 等)
|
|
145
|
+
- 复合组件配合 TemplateMode 使用
|
|
146
|
+
|
|
147
|
+
代码生成后询问用户:
|
|
148
|
+
> "代码已生成,你可以选择:
|
|
149
|
+
> 1. 调整样式或交互细节
|
|
150
|
+
> 2. 补充表单校验/批量操作等功能
|
|
151
|
+
> 3. 完成,收集反馈"
|
|
152
|
+
|
|
153
|
+
### 步骤7:收集反馈(必做)
|
|
154
|
+
|
|
155
|
+
询问准确率评分(1-5分)和是否采纳,调用 `mcp__szcd-component-helper__submit_feedback` 提交。
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"sessionId": "<时间戳>",
|
|
160
|
+
"toolType": "qoder",
|
|
161
|
+
"userQuery": "<用户原始需求>",
|
|
162
|
+
"generatedCodeSummary": "<核心组件列表>",
|
|
163
|
+
"accuracyRating": 4,
|
|
164
|
+
"adopted": true,
|
|
165
|
+
"contextSnapshot": {
|
|
166
|
+
"matchedComponents": ["Query", "TableOrList", "TemplateMode"],
|
|
167
|
+
"layoutType": "LeftRight"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 故障降级
|
|
173
|
+
|
|
174
|
+
MCP 工具调用失败时:
|
|
175
|
+
- 读取 `szcd-mcp-server/data/architecture-data.json` 获取架构数据
|
|
176
|
+
- 用 Grep/Read 直接查阅组件源码类型定义
|
|
177
|
+
- 输出标注"⚠️ 未通过 MCP 验证,API 可能有偏差"
|
|
@@ -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, getApiKey as getApiKeyDirect } from "./common.js";
|
|
12
|
+
import { getClientConfigHeader as getClientConfigHeaderDirect, getApiKey as getApiKeyDirect, getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
13
13
|
|
|
14
14
|
// ==================== 路径工具 ====================
|
|
15
15
|
|
|
@@ -363,9 +363,53 @@ export function setupClaudeCode(deps) {
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
// 最后注册额外的 MCP Server,避免被 CLI 操作覆盖
|
|
367
|
+
registerAdditionalMcpServers();
|
|
368
|
+
|
|
366
369
|
return {
|
|
367
370
|
claudeProjectInstalled,
|
|
368
371
|
skillsDirectory: getClaudeCodeSkillsDirectory(),
|
|
369
372
|
agentsDirectory: getClaudeCodeAgentsDirectory(),
|
|
370
373
|
};
|
|
371
374
|
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* 注册额外的 MCP Server 到 Claude Code
|
|
378
|
+
* HTTP 类型: { type: "http", url: "<httpUrl>" }
|
|
379
|
+
* stdio 类型: { type: "stdio", command, args }
|
|
380
|
+
* 注意:仅通过文件操作写入 ~/.claude.json,不使用 CLI,
|
|
381
|
+
* 因为 `claude mcp add` 会重写整个文件并丢失其他 server 条目
|
|
382
|
+
*/
|
|
383
|
+
function registerAdditionalMcpServers() {
|
|
384
|
+
const configPath = getClaudeCodeConfigPath();
|
|
385
|
+
let config = {};
|
|
386
|
+
if (fs.existsSync(configPath)) {
|
|
387
|
+
try {
|
|
388
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
389
|
+
} catch { /* 忽略 */ }
|
|
390
|
+
}
|
|
391
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
392
|
+
|
|
393
|
+
let changed = false;
|
|
394
|
+
for (const server of ADDITIONAL_MCP_SERVERS) {
|
|
395
|
+
let expected;
|
|
396
|
+
if (server.type === "http") {
|
|
397
|
+
const sketchUrl = `${getSketchMcpServerUrl()}/mcp`;
|
|
398
|
+
expected = { type: "http", url: sketchUrl };
|
|
399
|
+
} else {
|
|
400
|
+
expected = { type: server.type, command: server.command, args: server.args };
|
|
401
|
+
}
|
|
402
|
+
const current = config.mcpServers[server.name];
|
|
403
|
+
if (current && JSON.stringify(current) === JSON.stringify(expected)) {
|
|
404
|
+
console.log(`✓ Claude Code ~/.claude.json already has: ${server.name}`);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
config.mcpServers[server.name] = expected;
|
|
408
|
+
console.log(`✓ Added ${server.name} to Claude Code ~/.claude.json (${server.type})`);
|
|
409
|
+
changed = true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (changed) {
|
|
413
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
414
|
+
}
|
|
415
|
+
}
|
package/scripts/lib/common.js
CHANGED
|
@@ -17,6 +17,18 @@ const __dirname = path.dirname(__filename);
|
|
|
17
17
|
// 默认值(需在 SKILL_SOURCE 之前定义)
|
|
18
18
|
export const DEFAULT_MCP_SERVER_URL = "http://localhost:3456";
|
|
19
19
|
export const DEFAULT_MCP_SERVER_NAME = "szcd-component-helper";
|
|
20
|
+
export const DEFAULT_SKETCH_MCP_SERVER_URL = "http://localhost:3457";
|
|
21
|
+
|
|
22
|
+
// 额外的 MCP Server(HTTP 模式,由独立进程或代理提供 HTTP 接口)
|
|
23
|
+
export const ADDITIONAL_MCP_SERVERS = [
|
|
24
|
+
{
|
|
25
|
+
name: "sketch-mcp-server",
|
|
26
|
+
type: "stdio",
|
|
27
|
+
command: "sketch-mcp-server",
|
|
28
|
+
args: [],
|
|
29
|
+
description: "Sketch 设计文件解析(szcd 维护版),解析 .sketch 文件提取页面结构、图层信息和导出图片。postinstall 会尝试全局安装以加速启动",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
20
32
|
|
|
21
33
|
export const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
22
34
|
export const SKILLS_DIR = path.join(PACKAGE_ROOT, "standard-skill");
|
|
@@ -184,6 +196,11 @@ export function getMcpServerName() {
|
|
|
184
196
|
return config.MCP_SERVER_NAME || DEFAULT_MCP_SERVER_NAME;
|
|
185
197
|
}
|
|
186
198
|
|
|
199
|
+
export function getSketchMcpServerUrl() {
|
|
200
|
+
const config = loadExistingConfig();
|
|
201
|
+
return config.SKETCH_MCP_SERVER_URL || DEFAULT_SKETCH_MCP_SERVER_URL;
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
// ==================== 客户端 CODING 配置透传 ====================
|
|
188
205
|
|
|
189
206
|
/**
|
package/scripts/lib/opencode.js
CHANGED
|
@@ -39,7 +39,7 @@ import fs from "node:fs";
|
|
|
39
39
|
import path from "node:path";
|
|
40
40
|
import os from "node:os";
|
|
41
41
|
import { execSync } from "node:child_process";
|
|
42
|
-
import { getClientConfigHeader as _getClientConfigHeader, getApiKey as _getApiKey } from "./common.js";
|
|
42
|
+
import { getClientConfigHeader as _getClientConfigHeader, getApiKey as _getApiKey, getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
43
43
|
|
|
44
44
|
// ==================== 路径工具 ====================
|
|
45
45
|
|
|
@@ -97,10 +97,40 @@ function readJsonFile(filePath) {
|
|
|
97
97
|
if (!fs.existsSync(filePath)) return {};
|
|
98
98
|
try {
|
|
99
99
|
const content = fs.readFileSync(filePath, "utf8");
|
|
100
|
-
// jsonc
|
|
101
|
-
|
|
100
|
+
// jsonc 支持:去除注释,但需跳过字符串内的 //
|
|
101
|
+
// 策略:逐字符扫描,追踪是否在字符串内
|
|
102
|
+
const lines = content.split("\n");
|
|
103
|
+
const result = [];
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
let inString = false;
|
|
106
|
+
let escape = false;
|
|
107
|
+
let cleanLine = "";
|
|
108
|
+
for (let i = 0; i < line.length; i++) {
|
|
109
|
+
const ch = line[i];
|
|
110
|
+
if (escape) {
|
|
111
|
+
cleanLine += ch;
|
|
112
|
+
escape = false;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (ch === "\\") {
|
|
116
|
+
cleanLine += ch;
|
|
117
|
+
escape = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (ch === '"') {
|
|
121
|
+
inString = !inString;
|
|
122
|
+
cleanLine += ch;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!inString && ch === "/" && i + 1 < line.length && line[i + 1] === "/") {
|
|
126
|
+
break; // 行内注释开始,跳过剩余内容
|
|
127
|
+
}
|
|
128
|
+
cleanLine += ch;
|
|
129
|
+
}
|
|
130
|
+
result.push(cleanLine);
|
|
131
|
+
}
|
|
132
|
+
const cleaned = result.join("\n")
|
|
102
133
|
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
103
|
-
.replace(/\/\/.*$/gm, "")
|
|
104
134
|
.replace(/,(\s*[}\]])/g, "$1")
|
|
105
135
|
.trim();
|
|
106
136
|
return JSON.parse(cleaned);
|
|
@@ -164,16 +194,20 @@ function syncOpenCodeConfig(deps) {
|
|
|
164
194
|
|
|
165
195
|
const current = config.mcp[serverName];
|
|
166
196
|
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
197
|
+
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
167
198
|
const currentHeaders = current && current.headers ? current.headers : {};
|
|
168
|
-
const
|
|
199
|
+
const needsUpdate = !current || current.type !== "remote" || current.url !== mcpUrl
|
|
200
|
+
|| (apiKey && currentHeaders["X-API-Key"] !== apiKey)
|
|
201
|
+
|| (clientConfigHeader && currentHeaders["X-Client-Config"] !== clientConfigHeader);
|
|
169
202
|
|
|
170
|
-
if (current && current.type === "remote" && current.url === mcpUrl && !
|
|
203
|
+
if (current && current.type === "remote" && current.url === mcpUrl && !needsUpdate) {
|
|
171
204
|
console.log(`✓ OpenCode ${path.basename(actualPath)} already up-to-date: ${mcpUrl}`);
|
|
172
205
|
return;
|
|
173
206
|
}
|
|
174
207
|
|
|
175
208
|
const headers = {};
|
|
176
|
-
if (
|
|
209
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
210
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
177
211
|
|
|
178
212
|
config.mcp[serverName] = {
|
|
179
213
|
type: "remote",
|
|
@@ -212,8 +246,11 @@ function createOpenCodeProjectConfig(deps) {
|
|
|
212
246
|
|
|
213
247
|
const current = config.mcp[serverName];
|
|
214
248
|
const apiKey = deps.getApiKey ? deps.getApiKey() : "";
|
|
249
|
+
const clientConfigHeader = deps.getClientConfigHeader ? deps.getClientConfigHeader() : null;
|
|
215
250
|
const currentHeaders = current && current.headers ? current.headers : {};
|
|
216
|
-
const needsUpdate = !current || current.type !== "remote" || current.url !== mcpUrl
|
|
251
|
+
const needsUpdate = !current || current.type !== "remote" || current.url !== mcpUrl
|
|
252
|
+
|| (apiKey && currentHeaders["X-API-Key"] !== apiKey)
|
|
253
|
+
|| (clientConfigHeader && currentHeaders["X-Client-Config"] !== clientConfigHeader);
|
|
217
254
|
|
|
218
255
|
if (!needsUpdate) {
|
|
219
256
|
console.log(`✓ OpenCode project opencode.json already up-to-date: ${mcpUrl}`);
|
|
@@ -221,7 +258,8 @@ function createOpenCodeProjectConfig(deps) {
|
|
|
221
258
|
}
|
|
222
259
|
|
|
223
260
|
const headers = {};
|
|
224
|
-
if (
|
|
261
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
262
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
225
263
|
|
|
226
264
|
config.mcp[serverName] = {
|
|
227
265
|
type: "remote",
|
|
@@ -468,16 +506,20 @@ function syncOpenCodeConfigDirect(targetUrl, serverName) {
|
|
|
468
506
|
|
|
469
507
|
const current = config.mcp[serverName];
|
|
470
508
|
const apiKey = _getApiKey();
|
|
509
|
+
const clientConfigHeader = _getClientConfigHeader();
|
|
471
510
|
const currentHeaders = current && current.headers ? current.headers : {};
|
|
472
|
-
const
|
|
511
|
+
const needsUpdate = !current || current.type !== "remote" || current.url !== mcpUrl
|
|
512
|
+
|| (apiKey && currentHeaders["X-API-Key"] !== apiKey)
|
|
513
|
+
|| (clientConfigHeader && currentHeaders["X-Client-Config"] !== clientConfigHeader);
|
|
473
514
|
|
|
474
|
-
if (current && current.type === "remote" && current.url === mcpUrl && !
|
|
515
|
+
if (current && current.type === "remote" && current.url === mcpUrl && !needsUpdate) {
|
|
475
516
|
console.log(`⏭️ OpenCode ${path.basename(actualPath)} already up-to-date: ${mcpUrl}`);
|
|
476
517
|
return;
|
|
477
518
|
}
|
|
478
519
|
|
|
479
520
|
const headers = {};
|
|
480
|
-
if (
|
|
521
|
+
if (clientConfigHeader) headers["X-Client-Config"] = clientConfigHeader;
|
|
522
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
481
523
|
|
|
482
524
|
config.mcp[serverName] = {
|
|
483
525
|
type: "remote",
|
|
@@ -493,6 +535,57 @@ function syncOpenCodeConfigDirect(targetUrl, serverName) {
|
|
|
493
535
|
console.log(`✓ Updated OpenCode ${path.basename(actualPath)}: ${mcpUrl}`);
|
|
494
536
|
}
|
|
495
537
|
|
|
538
|
+
// ==================== 额外 MCP Server(local 模式) ====================
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* 将 ADDITIONAL_MCP_SERVERS 中的 MCP server 注册到 ~/.config/opencode/opencode.jsonc 的 mcp 中。
|
|
542
|
+
* HTTP 类型: { type: "remote", url: "<httpUrl>" }
|
|
543
|
+
* stdio 类型: { type: "local", command: ["sketch-mcp-server"] }
|
|
544
|
+
* 如果已存在同名 server 则跳过,不覆盖已有配置。
|
|
545
|
+
*/
|
|
546
|
+
function registerAdditionalMcpServers() {
|
|
547
|
+
const configPath = getOpenCodeConfigPath();
|
|
548
|
+
const fallbackPath = getOpenCodeConfigPathJson();
|
|
549
|
+
let actualPath = fs.existsSync(configPath) ? configPath : fallbackPath;
|
|
550
|
+
|
|
551
|
+
let config = {};
|
|
552
|
+
if (fs.existsSync(actualPath)) {
|
|
553
|
+
config = readJsonFile(actualPath);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!config.mcp) config.mcp = {};
|
|
557
|
+
|
|
558
|
+
let changed = false;
|
|
559
|
+
for (const server of ADDITIONAL_MCP_SERVERS) {
|
|
560
|
+
if (config.mcp[server.name]) {
|
|
561
|
+
console.log(`✓ OpenCode ${path.basename(actualPath)} already has MCP server: ${server.name}, skipping`);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (server.type === "http") {
|
|
565
|
+
const sketchUrl = `${getSketchMcpServerUrl()}/mcp`;
|
|
566
|
+
config.mcp[server.name] = {
|
|
567
|
+
type: "remote",
|
|
568
|
+
url: sketchUrl,
|
|
569
|
+
};
|
|
570
|
+
console.log(`✓ Registered additional MCP server to OpenCode: ${server.name} (remote, ${sketchUrl})`);
|
|
571
|
+
} else {
|
|
572
|
+
config.mcp[server.name] = {
|
|
573
|
+
type: "local",
|
|
574
|
+
command: [server.command, ...server.args],
|
|
575
|
+
};
|
|
576
|
+
console.log(`✓ Registered additional MCP server to OpenCode: ${server.name} (local)`);
|
|
577
|
+
}
|
|
578
|
+
changed = true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (changed) {
|
|
582
|
+
if (!config.$schema) {
|
|
583
|
+
config.$schema = "https://opencode.ai/config.json";
|
|
584
|
+
}
|
|
585
|
+
writeJsonFile(actualPath, config);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
496
589
|
// ==================== 导出 ====================
|
|
497
590
|
|
|
498
591
|
export function setupOpenCode(deps) {
|
|
@@ -517,6 +610,9 @@ export function setupOpenCode(deps) {
|
|
|
517
610
|
}
|
|
518
611
|
*/
|
|
519
612
|
|
|
613
|
+
// ---- 额外 MCP Server(local 模式) ----
|
|
614
|
+
registerAdditionalMcpServers();
|
|
615
|
+
|
|
520
616
|
return {
|
|
521
617
|
openCodeProjectInstalled: false,
|
|
522
618
|
skillsDirectory: getOpenCodeSkillsDirectory(),
|
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, getApiKey as _getApiKey } from "./common.js";
|
|
32
|
+
import { getClientConfigHeader as _getClientConfigHeader, getApiKey as _getApiKey, getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
33
33
|
|
|
34
34
|
// ==================== 路径工具 ====================
|
|
35
35
|
|
|
@@ -474,6 +474,44 @@ function syncQoderSettingsDirect(targetUrl, serverName) {
|
|
|
474
474
|
console.log(`✓ Updated Qoder ~/.qoder/settings.json: ${serverName} (${mcpUrl})`);
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
+
// ==================== 额外 MCP Server(stdio 模式) ====================
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* 将 ADDITIONAL_MCP_SERVERS 中的 MCP server 注册到 ~/.qoder/settings.json 的 mcpServers 中。
|
|
481
|
+
* HTTP 类型: { type: "http", url: "<httpUrl>" }
|
|
482
|
+
* stdio 类型: { type: "stdio", command, args }
|
|
483
|
+
* 如果已存在同名 server 则跳过,不覆盖已有配置。
|
|
484
|
+
*/
|
|
485
|
+
function registerAdditionalMcpServers() {
|
|
486
|
+
const settingsPath = getQoderSettingsPath();
|
|
487
|
+
let settings = readJsonFile(settingsPath);
|
|
488
|
+
|
|
489
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
490
|
+
|
|
491
|
+
for (const server of ADDITIONAL_MCP_SERVERS) {
|
|
492
|
+
if (settings.mcpServers[server.name]) {
|
|
493
|
+
console.log(`✓ Qoder ~/.qoder/settings.json already has MCP server: ${server.name}, skipping`);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (server.type === "http") {
|
|
497
|
+
const sketchUrl = `${getSketchMcpServerUrl()}/mcp`;
|
|
498
|
+
settings.mcpServers[server.name] = {
|
|
499
|
+
type: "http",
|
|
500
|
+
url: sketchUrl,
|
|
501
|
+
};
|
|
502
|
+
} else {
|
|
503
|
+
settings.mcpServers[server.name] = {
|
|
504
|
+
type: server.type,
|
|
505
|
+
command: server.command,
|
|
506
|
+
args: server.args,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
console.log(`✓ Registered additional MCP server to Qoder: ${server.name} (${server.type})`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
writeJsonFile(settingsPath, settings);
|
|
513
|
+
}
|
|
514
|
+
|
|
477
515
|
// ==================== 导出 ====================
|
|
478
516
|
|
|
479
517
|
export function setupQoder(deps) {
|
|
@@ -504,6 +542,9 @@ export function setupQoder(deps) {
|
|
|
504
542
|
// }
|
|
505
543
|
// }
|
|
506
544
|
|
|
545
|
+
// ---- 额外 MCP Server(stdio 模式) ----
|
|
546
|
+
registerAdditionalMcpServers();
|
|
547
|
+
|
|
507
548
|
return {
|
|
508
549
|
qoderProjectInstalled: false,
|
|
509
550
|
skillsDirectory: getQoderSkillsDirectory(),
|
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, getApiKey } from "./common.js";
|
|
17
|
+
import { loadClientCodingConfig, encodeClientConfig, getApiKey, getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
18
18
|
|
|
19
19
|
// ==================== 路径工具 ====================
|
|
20
20
|
|
|
@@ -163,10 +163,48 @@ export function createTraeCliConfig(deps) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
syncTraeCliYaml(deps);
|
|
166
|
+
registerAdditionalMcpServersToYaml();
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
// ==================== Skill 复制(Trae CLI) ====================
|
|
169
170
|
|
|
171
|
+
/**
|
|
172
|
+
* 注册额外的 MCP Server 到 Trae CLI YAML
|
|
173
|
+
* HTTP 类型: { type: http, url: "<httpUrl>" }
|
|
174
|
+
* stdio 类型: { type: stdio, command, args }
|
|
175
|
+
*/
|
|
176
|
+
function registerAdditionalMcpServersToYaml() {
|
|
177
|
+
const yamlPath = getTraeCliYamlPath();
|
|
178
|
+
if (!fs.existsSync(yamlPath)) return;
|
|
179
|
+
|
|
180
|
+
let content = fs.readFileSync(yamlPath, "utf8");
|
|
181
|
+
const { servers } = parseYamlMcpServers(content);
|
|
182
|
+
|
|
183
|
+
for (const server of ADDITIONAL_MCP_SERVERS) {
|
|
184
|
+
const existing = servers.find(s => s.name === server.name);
|
|
185
|
+
if (existing) {
|
|
186
|
+
console.log(`✓ Trae CLI YAML already has: ${server.name}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let entry;
|
|
190
|
+
if (server.type === "http") {
|
|
191
|
+
const sketchUrl = `${getSketchMcpServerUrl()}/mcp`;
|
|
192
|
+
entry = ` - name: ${server.name}\n type: http\n url: ${sketchUrl}\n`;
|
|
193
|
+
} else {
|
|
194
|
+
const argsStr = server.args ? server.args.map(a => `"${a}"`).join(", ") : "";
|
|
195
|
+
entry = ` - name: ${server.name}\n type: ${server.type}\n command: ${server.command}\n args: [${argsStr}]\n`;
|
|
196
|
+
}
|
|
197
|
+
if (/^mcp_servers\s*:/m.test(content)) {
|
|
198
|
+
content = content.replace(/^(mcp_servers\s*:)/m, `$1\n${entry}`);
|
|
199
|
+
} else {
|
|
200
|
+
content = content.trimEnd() + `\n\nmcp_servers:\n${entry}`;
|
|
201
|
+
}
|
|
202
|
+
console.log(`✓ Added ${server.name} to Trae CLI YAML (${server.type})`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fs.writeFileSync(yamlPath, content);
|
|
206
|
+
}
|
|
207
|
+
|
|
170
208
|
export function getTraeCliSkillsDirectory() {
|
|
171
209
|
return path.join(getHomeDir(), ".traecli", "skills");
|
|
172
210
|
}
|
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, getApiKey } from "./common.js";
|
|
16
|
+
import { getClientConfigHeader, getApiKey, getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
17
17
|
|
|
18
18
|
// ==================== 路径工具 ====================
|
|
19
19
|
|
|
@@ -101,6 +101,45 @@ export function syncTraeCnMcpJson(deps) {
|
|
|
101
101
|
|
|
102
102
|
export function createTraeIdeConfig(deps) {
|
|
103
103
|
syncTraeCnMcpJson(deps);
|
|
104
|
+
registerAdditionalMcpServers();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 注册额外的 MCP Server,如 sketch-mcp-server
|
|
109
|
+
* HTTP 类型: { type: "http", url: "<httpUrl>" }
|
|
110
|
+
* stdio 类型: { type: "stdio", command, args }
|
|
111
|
+
*/
|
|
112
|
+
function registerAdditionalMcpServers() {
|
|
113
|
+
const mcpJsonPath = getTraeIdeMcpJsonPath();
|
|
114
|
+
|
|
115
|
+
let config = {};
|
|
116
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
117
|
+
try {
|
|
118
|
+
config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"));
|
|
119
|
+
} catch { /* 忽略 */ }
|
|
120
|
+
}
|
|
121
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
122
|
+
|
|
123
|
+
for (const server of ADDITIONAL_MCP_SERVERS) {
|
|
124
|
+
let expected;
|
|
125
|
+
if (server.type === "http") {
|
|
126
|
+
const sketchUrl = `${getSketchMcpServerUrl()}/mcp`;
|
|
127
|
+
expected = { type: "http", url: sketchUrl };
|
|
128
|
+
} else {
|
|
129
|
+
expected = { type: server.type, command: server.command, args: server.args };
|
|
130
|
+
}
|
|
131
|
+
const current = config.mcpServers[server.name];
|
|
132
|
+
if (current && JSON.stringify(current) === JSON.stringify(expected)) {
|
|
133
|
+
console.log(`✓ Trae CN mcp.json already has: ${server.name}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
config.mcpServers[server.name] = expected;
|
|
137
|
+
console.log(`✓ Added ${server.name} to Trae CN mcp.json (${server.type})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const dir = path.dirname(mcpJsonPath);
|
|
141
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
142
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2));
|
|
104
143
|
}
|
|
105
144
|
|
|
106
145
|
// ==================== MCP URL 同步(供 update-mcp-url.js 调用) ====================
|
package/scripts/postinstall.js
CHANGED
|
@@ -46,6 +46,31 @@ const deps = {
|
|
|
46
46
|
PACKAGE_ROOT: common.PACKAGE_ROOT,
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
// ==================== sketch-mcp-server 全局安装 ====================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 确保 @szc-ft/sketch-mcp-server 已全局安装(提供 sketch-mcp-server 命令行工具)
|
|
53
|
+
* 如果未安装,自动执行 npm install -g @szc-ft/sketch-mcp-server
|
|
54
|
+
* 这样各 IDE 通过全局 bin 调用 sketch-mcp-server,避免 npx 网络延迟
|
|
55
|
+
*/
|
|
56
|
+
function ensureSketchMcpServer(deps) {
|
|
57
|
+
if (deps.isCommandAvailable("sketch-mcp-server")) {
|
|
58
|
+
console.log("✓ sketch-mcp-server already installed globally");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log("📦 Installing @szc-ft/sketch-mcp-server globally (npm install -g @szc-ft/sketch-mcp-server)...");
|
|
63
|
+
console.log(" This avoids npx network delays when IDE starts the MCP server.");
|
|
64
|
+
|
|
65
|
+
const result = deps.safeExecSync("npm install -g @szc-ft/sketch-mcp-server", { timeout: 30000 });
|
|
66
|
+
if (result === true) {
|
|
67
|
+
console.log("✓ @szc-ft/sketch-mcp-server installed globally successfully");
|
|
68
|
+
} else {
|
|
69
|
+
console.warn("⚠️ Failed to install @szc-ft/sketch-mcp-server globally.");
|
|
70
|
+
console.warn(" You can install it manually: npm install -g @szc-ft/sketch-mcp-server");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
// ==================== 快速模式提示 ====================
|
|
50
75
|
|
|
51
76
|
function showQuickMessage() {
|
|
@@ -179,6 +204,9 @@ function main() {
|
|
|
179
204
|
|
|
180
205
|
// ---- 配置文件 ----
|
|
181
206
|
createConfigTemplate();
|
|
207
|
+
console.log("\n🔧 Ensuring sketch-mcp-server is installed globally...\n");
|
|
208
|
+
ensureSketchMcpServer(deps);
|
|
209
|
+
|
|
182
210
|
console.log("\n🔧 Setting up OpenCode compatibility...\n");
|
|
183
211
|
const openCodeResult = setupOpenCode(deps);
|
|
184
212
|
|