@szc-ft/mcp-szcd-client 0.23.1 → 0.25.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.
Files changed (31) hide show
  1. package/agents/build.js +22 -12
  2. package/agents/opencode-extension/agents/szcd-component-expert.md +94 -50
  3. package/agents/qwen-extension/agents/szcd-component-expert.md +94 -50
  4. package/agents/src/szcd-component-expert.md +94 -50
  5. package/agents/src/tools.json +1 -1
  6. package/agents/szcd-component-expert.md +94 -50
  7. package/agents/szcd-component-expert.qoder.md +95 -51
  8. package/agents/szcd-component-expert.trae.md +94 -50
  9. package/opencode-extension/agents/szcd-component-expert.md +94 -50
  10. package/opencode-extension/skills/local-api-tool/SKILL.md +164 -60
  11. package/opencode-extension/skills/local-api-tool/scripts/extract_swagger.py +477 -0
  12. package/opencode-extension/skills/szcd-component-helper/SKILL.md +22 -17
  13. package/opencode-extension/skills/szcd-design-to-code/SKILL.md +141 -31
  14. package/package.json +1 -1
  15. package/qwen-extension/agents/szcd-component-expert.md +94 -50
  16. package/qwen-extension/qwen-extension.json +1 -1
  17. package/qwen-extension/skills/local-api-tool/SKILL.md +164 -60
  18. package/qwen-extension/skills/local-api-tool/scripts/extract_swagger.py +477 -0
  19. package/qwen-extension/skills/szcd-component-helper/SKILL.md +22 -17
  20. package/qwen-extension/skills/szcd-design-to-code/SKILL.md +141 -31
  21. package/scripts/lib/claude-code.js +1 -1
  22. package/scripts/lib/common.js +44 -0
  23. package/scripts/lib/opencode.js +1 -1
  24. package/scripts/lib/qoder.js +1 -1
  25. package/scripts/lib/trae-cli.js +1 -1
  26. package/scripts/lib/trae-ide.js +1 -1
  27. package/scripts/postinstall.js +1 -0
  28. package/standard-skill/local-api-tool/SKILL.md +164 -60
  29. package/standard-skill/local-api-tool/scripts/extract_swagger.py +477 -0
  30. package/standard-skill/szcd-component-helper/SKILL.md +22 -17
  31. package/standard-skill/szcd-design-to-code/SKILL.md +141 -31
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ extract_swagger.py — 本地 Swagger 文档提取与解析(与服务端 parseApiDocs 一比一对齐)
4
+
5
+ 设计目标:
6
+ - 一步完成"下载 → 解析 → 按 tag/path 过滤 → 关联 DTO definitions → 输出"
7
+ - 避免 AI 在客户端用 curl|head 截断 JSON / 写临时 python 脚本踩花括号坑
8
+ - 输出结构与服务端 api_tool(action="parse_swagger_json") 完全一致,可平替
9
+
10
+ 参考实现:szcd-mcp-server/src/lib/swagger.js 的 parseApiDocs + server.js 的 filterApis
11
+ 递归深度 5,两层 definitions(summary + detail),引用树传递闭包裁剪。
12
+
13
+ 用法:
14
+ # 方式 A:脚本内部 curl 下载
15
+ extract_swagger.py --url http://10.x.x.x/svc/v2/api-docs [--cookie 'JSESSIONID=xxx']
16
+
17
+ # 方式 B:读取本地已下载的 JSON 文件
18
+ extract_swagger.py --file /tmp/swagger.json
19
+
20
+ # 过滤(对 url/summary/operationId/tag/description/dtoRef/responseVoName 模糊匹配,大小写不敏感)
21
+ extract_swagger.py --file /tmp/swagger.json --filter tag
22
+ extract_swagger.py --file /tmp/swagger.json --filter tag --filter user
23
+
24
+ # 仅查询单个 definition(兜底单 VO 查询,等价于服务端 get_definition)
25
+ extract_swagger.py --file /tmp/swagger.json --definition KnowledgeOperLogVO
26
+
27
+ # 输出到文件而非 stdout
28
+ extract_swagger.py --file /tmp/swagger.json --filter tag --out /tmp/parsed.json
29
+
30
+ 退出码:0 成功;1 参数错误;2 网络/IO 错误;3 解析错误
31
+ """
32
+
33
+ import argparse
34
+ import json
35
+ import sys
36
+ import subprocess
37
+ from pathlib import Path
38
+
39
+ MAX_DEPTH = 5
40
+ METHOD_KEYS = ["post", "get", "put", "delete", "patch"]
41
+
42
+
43
+ # ==================== 工具函数 ====================
44
+
45
+ def _ref_name(prop):
46
+ """从 property 定义中提取 $ref/originalRef 名称(不含数组 items 层)。"""
47
+ if not isinstance(prop, dict):
48
+ return None
49
+ if prop.get("originalRef"):
50
+ return prop["originalRef"]
51
+ ref = prop.get("$ref")
52
+ if ref:
53
+ return ref.rsplit("/", 1)[-1]
54
+ return None
55
+
56
+
57
+ def _ref_name_with_items(prop):
58
+ """提取 $ref 名称,包含数组 items 层(对齐 JS 端 expandProperties 第 250-251 行)。"""
59
+ if not isinstance(prop, dict):
60
+ return None
61
+ name = _ref_name(prop)
62
+ if name:
63
+ return name
64
+ items = prop.get("items")
65
+ if isinstance(items, dict):
66
+ return _ref_name(items)
67
+ return None
68
+
69
+
70
+ def _resolve_property_type(prop):
71
+ """对齐 JS 端 resolvePropertyType:数组返回 array<XXX>,否则返回 type 或 $ref 名。"""
72
+ if not isinstance(prop, dict):
73
+ return "string"
74
+ if prop.get("type") == "array":
75
+ items = prop.get("items") or {}
76
+ item_type = (
77
+ items.get("type")
78
+ or items.get("originalRef")
79
+ or (items.get("$ref", "").rsplit("/", 1)[-1] if items.get("$ref") else None)
80
+ or "object"
81
+ )
82
+ return f"array<{item_type}>"
83
+ if prop.get("type"):
84
+ return prop["type"]
85
+ if prop.get("originalRef"):
86
+ return prop["originalRef"]
87
+ if prop.get("$ref"):
88
+ return prop["$ref"].rsplit("/", 1)[-1]
89
+ return "object"
90
+
91
+
92
+ # ==================== 递归展开(对齐 JS 端 expandProperties,深度 5) ====================
93
+
94
+ def expand_properties(properties, definitions, depth=0, max_depth=MAX_DEPTH):
95
+ if not properties:
96
+ return []
97
+ result = []
98
+ for name, prop in properties.items():
99
+ if not isinstance(prop, dict):
100
+ prop = {}
101
+ entry = {
102
+ "name": name,
103
+ "type": _resolve_property_type(prop),
104
+ "description": prop.get("description", ""),
105
+ }
106
+ if "format" in prop:
107
+ entry["format"] = prop["format"]
108
+ if "example" in prop:
109
+ entry["example"] = prop["example"]
110
+ if "enum" in prop:
111
+ entry["enum"] = prop["enum"]
112
+
113
+ ref_name = _ref_name_with_items(prop)
114
+ if ref_name:
115
+ entry["refName"] = ref_name
116
+
117
+ # 数组类型:展开 items 的嵌套属性
118
+ if prop.get("type") == "array" and ref_name and depth < max_depth:
119
+ item_def = (definitions or {}).get(ref_name)
120
+ if isinstance(item_def, dict) and item_def.get("properties"):
121
+ entry["itemProperties"] = expand_properties(
122
+ item_def["properties"], definitions, depth + 1, max_depth
123
+ )
124
+
125
+ # 对象类型(非数组):展开嵌套属性
126
+ if prop.get("type") == "object" and ref_name and depth < max_depth:
127
+ nested_def = (definitions or {}).get(ref_name)
128
+ if isinstance(nested_def, dict) and nested_def.get("properties"):
129
+ entry["nestedProperties"] = expand_properties(
130
+ nested_def["properties"], definitions, depth + 1, max_depth
131
+ )
132
+
133
+ # 无 $ref 的 object 且有自身 properties(内联定义)
134
+ if (
135
+ prop.get("type") == "object"
136
+ and not ref_name
137
+ and prop.get("properties")
138
+ and depth < max_depth
139
+ ):
140
+ entry["nestedProperties"] = expand_properties(
141
+ prop["properties"], definitions, depth + 1, max_depth
142
+ )
143
+
144
+ result.append(entry)
145
+ return result
146
+
147
+
148
+ # ==================== 主解析(对齐 JS 端 parseApiDocs) ====================
149
+
150
+ def parse_api_docs(api_docs, referenced_defs=None):
151
+ paths = api_docs.get("paths") or {}
152
+ tags = api_docs.get("tags") or []
153
+ definitions = api_docs.get("definitions") or {}
154
+ info = api_docs.get("info") or {}
155
+ api_base_path = api_docs.get("basePath") or ""
156
+
157
+ parsed_tags = [
158
+ {"name": t.get("name"), "description": t.get("description", "")} for t in tags
159
+ ]
160
+
161
+ parsed_apis = []
162
+ auto_referenced_defs = set()
163
+
164
+ for url, path_value in paths.items():
165
+ if not isinstance(path_value, dict):
166
+ continue
167
+ for method in METHOD_KEYS:
168
+ api_def = path_value.get(method)
169
+ if not isinstance(api_def, dict):
170
+ continue
171
+ tag = (api_def.get("tags") or ["default"])[0]
172
+ parameters = api_def.get("parameters") or []
173
+
174
+ params = []
175
+ for p in parameters:
176
+ schema = p.get("schema") or {}
177
+ param_info = {
178
+ "name": p.get("name"),
179
+ "in": p.get("in"),
180
+ "required": p.get("required", False),
181
+ "type": p.get("type") or schema.get("type") or "string",
182
+ "description": p.get("description", ""),
183
+ }
184
+ # body 参数引用的 DTO
185
+ ref_name = schema.get("originalRef") or (
186
+ schema.get("$ref", "").rsplit("/", 1)[-1] if schema.get("$ref") else None
187
+ )
188
+ if ref_name:
189
+ param_info["dtoRef"] = ref_name
190
+ auto_referenced_defs.add(ref_name)
191
+ dto = definitions.get(ref_name)
192
+ if isinstance(dto, dict) and dto.get("properties"):
193
+ dto_props = expand_properties(dto["properties"], definitions, 0, MAX_DEPTH)
194
+ required_fields = dto.get("required") or []
195
+ for prop in dto_props:
196
+ if prop["name"] in required_fields:
197
+ prop["required"] = True
198
+ param_info["dtoProperties"] = dto_props
199
+ params.append(param_info)
200
+
201
+ # 响应(递归展开嵌套 VO)
202
+ response_info = None
203
+ responses = api_def.get("responses") or {}
204
+ r200 = responses.get("200") or {}
205
+ r_schema = r200.get("schema") if isinstance(r200, dict) else None
206
+ if isinstance(r_schema, dict):
207
+ ref_name = r_schema.get("originalRef") or (
208
+ r_schema.get("$ref", "").rsplit("/", 1)[-1] if r_schema.get("$ref") else None
209
+ )
210
+ if ref_name:
211
+ auto_referenced_defs.add(ref_name)
212
+ vo = definitions.get(ref_name)
213
+ if isinstance(vo, dict) and vo.get("properties"):
214
+ response_info = {
215
+ "voName": ref_name,
216
+ "properties": expand_properties(
217
+ vo["properties"], definitions, 0, MAX_DEPTH
218
+ ),
219
+ }
220
+ else:
221
+ response_info = {"voName": ref_name}
222
+
223
+ parsed_apis.append({
224
+ "url": url,
225
+ "method": method.upper(),
226
+ "tag": tag,
227
+ "summary": api_def.get("summary", ""),
228
+ "description": api_def.get("description", ""),
229
+ "deprecated": api_def.get("deprecated", False),
230
+ "parameters": params,
231
+ "response": response_info,
232
+ "operationId": api_def.get("operationId"),
233
+ })
234
+
235
+ # 收集被引用 definitions 的传递闭包
236
+ collected_refs = set(auto_referenced_defs)
237
+ queue = list(auto_referenced_defs)
238
+ while queue:
239
+ name = queue.pop(0)
240
+ d = definitions.get(name)
241
+ if not isinstance(d, dict) or not d.get("properties"):
242
+ continue
243
+ for prop in d["properties"].values():
244
+ ref_name = _ref_name_with_items(prop)
245
+ if ref_name and ref_name in definitions and ref_name not in collected_refs:
246
+ collected_refs.add(ref_name)
247
+ queue.append(ref_name)
248
+
249
+ # definitions 摘要层:所有 VO/DTO 的 name + type + description
250
+ definitions_summary = [
251
+ {
252
+ "name": name,
253
+ "type": d.get("type", "object") if isinstance(d, dict) else "object",
254
+ "description": (d.get("description") or d.get("title") or "") if isinstance(d, dict) else "",
255
+ }
256
+ for name, d in definitions.items()
257
+ ]
258
+
259
+ # definitionsDetail 详情层:仅展开被引用的(含传递闭包),递归深度 5
260
+ effective_refs = referenced_defs if referenced_defs is not None else collected_refs
261
+ definitions_detail = []
262
+ for name in effective_refs:
263
+ d = definitions.get(name)
264
+ if isinstance(d, dict):
265
+ definitions_detail.append({
266
+ "name": name,
267
+ "type": d.get("type", "object"),
268
+ "description": d.get("description") or d.get("title") or "",
269
+ "properties": expand_properties(
270
+ d.get("properties") or {}, definitions, 0, MAX_DEPTH
271
+ ),
272
+ })
273
+
274
+ return {
275
+ "info": {
276
+ "title": info.get("title"),
277
+ "version": info.get("version"),
278
+ "description": info.get("description"),
279
+ } if info else None,
280
+ "basePath": api_base_path,
281
+ "tags": parsed_tags,
282
+ "apis": parsed_apis,
283
+ "definitions": definitions_summary,
284
+ "definitionsDetail": definitions_detail,
285
+ }
286
+
287
+
288
+ # ==================== 过滤(对齐 JS 端 filterApis + apiFilter 裁剪 definitionsDetail) ====================
289
+
290
+ def filter_apis(apis, keywords):
291
+ """对齐 JS 端 filterApis:对 url/summary/operationId/tag/description/dtoRef/voName 模糊匹配。"""
292
+ if not keywords or not apis:
293
+ return apis
294
+ lower_keywords = [k.lower() for k in keywords]
295
+ out = []
296
+ for api in apis:
297
+ dto_refs = " ".join(p.get("dtoRef", "") for p in (api.get("parameters") or []) if p.get("dtoRef"))
298
+ response_vo = (api.get("response") or {}).get("voName", "") if api.get("response") else ""
299
+ searchable = " ".join([
300
+ api.get("url", ""),
301
+ api.get("summary", ""),
302
+ api.get("operationId") or "",
303
+ api.get("tag", ""),
304
+ api.get("description", ""),
305
+ dto_refs,
306
+ response_vo,
307
+ ]).lower()
308
+ if any(kw in searchable for kw in lower_keywords):
309
+ out.append(api)
310
+ return out
311
+
312
+
313
+ def trim_definitions_detail(result, keywords):
314
+ """apiFilter 生效时,definitionsDetail 仅保留匹配 API 引用的(含传递闭包)。
315
+ 对齐 JS 端 server.js 第 1581-1604 行的裁剪逻辑。"""
316
+ apis = result.get("apis") or []
317
+ matched_refs = set()
318
+ for api in apis:
319
+ for p in api.get("parameters") or []:
320
+ if p.get("dtoRef"):
321
+ matched_refs.add(p["dtoRef"])
322
+ if api.get("response") and api["response"].get("voName"):
323
+ matched_refs.add(api["response"]["voName"])
324
+
325
+ detail_index = {d["name"]: d for d in result.get("definitionsDetail") or []}
326
+ detail_names = set(matched_refs)
327
+ queue = list(matched_refs)
328
+ while queue:
329
+ name = queue.pop(0)
330
+ detail = detail_index.get(name)
331
+ if not detail or not detail.get("properties"):
332
+ continue
333
+ for prop in detail["properties"]:
334
+ ref = prop.get("refName")
335
+ if ref and ref not in detail_names:
336
+ detail_names.add(ref)
337
+ queue.append(ref)
338
+
339
+ result["definitionsDetail"] = [d for d in result.get("definitionsDetail") or [] if d["name"] in detail_names]
340
+ result["_filter"] = {
341
+ "keywords": keywords,
342
+ "matched": len(apis),
343
+ }
344
+ return result
345
+
346
+
347
+ # ==================== get_definition(单 VO 查询兜底) ====================
348
+
349
+ def get_definition(api_docs, definition_name):
350
+ """对齐 JS 端 get_definition action:按名查询单个 VO,找不到给模糊匹配建议。"""
351
+ definitions = api_docs.get("definitions") or {}
352
+ if definition_name in definitions:
353
+ result = parse_api_docs(api_docs, referenced_defs={definition_name})
354
+ target = next((d for d in result["definitionsDetail"] if d["name"] == definition_name), None)
355
+ return {"found": True, "definition": target}
356
+
357
+ # 模糊匹配
358
+ lower = definition_name.lower()
359
+ suggestions = [n for n in definitions.keys() if lower in n.lower()][:10]
360
+ return {
361
+ "found": False,
362
+ "definitionName": definition_name,
363
+ "suggestions": suggestions,
364
+ "message": f"未找到 definition '{definition_name}'。" + (
365
+ f"相近建议:{', '.join(suggestions)}" if suggestions else "无相近匹配。"
366
+ ),
367
+ }
368
+
369
+
370
+ # ==================== 输入:curl 下载 / 读文件 ====================
371
+
372
+ def fetch_via_curl(url, cookie=None, auth=None, timeout=30):
373
+ """脚本内部走 curl 下载 Swagger JSON。失败抛 RuntimeError。"""
374
+ cmd = ["curl", "-sS", "--connect-timeout", "10", "--max-time", str(timeout),
375
+ "-H", "Accept: application/json"]
376
+ if cookie:
377
+ cmd += ["-H", f"Cookie: {cookie}"]
378
+ if auth:
379
+ cmd += ["-H", f"Authorization: Basic {auth}"]
380
+ cmd.append(url)
381
+ try:
382
+ r = subprocess.run(cmd, capture_output=True, text=True, check=False)
383
+ except FileNotFoundError:
384
+ raise RuntimeError("找不到 curl 命令,请确认本地已安装 curl") from None
385
+ if r.returncode != 0:
386
+ raise RuntimeError(f"curl 失败 (exit {r.returncode}): {r.stderr.strip() or '无错误输出'}")
387
+ if not r.stdout.strip():
388
+ raise RuntimeError("curl 返回空响应")
389
+ return r.stdout
390
+
391
+
392
+ def load_json_text(text):
393
+ try:
394
+ return json.loads(text)
395
+ except json.JSONDecodeError as e:
396
+ # 给出第一行 200 字符 + 错误位置,方便排查
397
+ preview = text[:200].replace("\n", "\\n")
398
+ raise RuntimeError(f"JSON 解析失败: {e.msg} (line {e.lineno} col {e.colno})。响应预览: {preview}") from None
399
+
400
+
401
+ # ==================== CLI ====================
402
+
403
+ def main():
404
+ parser = argparse.ArgumentParser(
405
+ description="本地 Swagger 文档提取与解析(与服务端 parseApiDocs 一比一对齐)",
406
+ formatter_class=argparse.RawDescriptionHelpFormatter,
407
+ epilog=__doc__,
408
+ )
409
+ src = parser.add_mutually_exclusive_group(required=True)
410
+ src.add_argument("--url", help="Swagger api-docs URL(脚本内部走 curl 下载)")
411
+ src.add_argument("--file", help="本地 Swagger JSON 文件路径")
412
+
413
+ parser.add_argument("--cookie", help="--url 模式可选,鉴权后的 Cookie 值(如 'JSESSIONID=xxx')")
414
+ parser.add_argument("--auth", help="--url 模式可选,Basic 鉴权 base64(user:pass)")
415
+ parser.add_argument("--timeout", type=int, default=30, help="--url 模式 curl 超时秒数,默认 30")
416
+
417
+ parser.add_argument(
418
+ "--filter", action="append", default=[],
419
+ help="按关键词过滤 api(可多次指定,对 url/summary/operationId/tag/description/dtoRef/voName 模糊匹配)",
420
+ )
421
+ parser.add_argument(
422
+ "--definition",
423
+ help="仅查询单个 VO/DTO 的完整字段定义(等价于服务端 get_definition action)",
424
+ )
425
+ parser.add_argument("--out", help="输出到文件(默认 stdout)")
426
+ parser.add_argument(
427
+ "--indent", type=int, default=2,
428
+ help="JSON 输出缩进,默认 2;设为 0 输出紧凑 JSON",
429
+ )
430
+
431
+ args = parser.parse_args()
432
+
433
+ # 1. 读取原始 Swagger JSON
434
+ try:
435
+ if args.url:
436
+ raw_text = fetch_via_curl(args.url, cookie=args.cookie, auth=args.auth, timeout=args.timeout)
437
+ else:
438
+ raw_text = Path(args.file).read_text(encoding="utf-8")
439
+ except Exception as e:
440
+ print(f"[ERROR] 读取 Swagger 失败: {e}", file=sys.stderr)
441
+ sys.exit(2)
442
+
443
+ # 2. 解析 JSON
444
+ try:
445
+ api_docs = load_json_text(raw_text)
446
+ except Exception as e:
447
+ print(f"[ERROR] {e}", file=sys.stderr)
448
+ sys.exit(3)
449
+
450
+ # 3. 路径分支:--definition 单查 / 默认完整解析
451
+ try:
452
+ if args.definition:
453
+ result = get_definition(api_docs, args.definition)
454
+ else:
455
+ result = parse_api_docs(api_docs)
456
+ if args.filter:
457
+ total = len(result["apis"])
458
+ result["apis"] = filter_apis(result["apis"], args.filter)
459
+ result = trim_definitions_detail(result, args.filter)
460
+ result["_filter"]["total"] = total
461
+ except Exception as e:
462
+ print(f"[ERROR] 解析失败: {e}", file=sys.stderr)
463
+ sys.exit(3)
464
+
465
+ # 4. 输出
466
+ indent = args.indent if args.indent > 0 else None
467
+ out_text = json.dumps(result, ensure_ascii=False, indent=indent)
468
+ if args.out:
469
+ Path(args.out).write_text(out_text, encoding="utf-8")
470
+ print(f"[OK] 已输出到 {args.out} ({len(out_text)} bytes)", file=sys.stderr)
471
+ else:
472
+ sys.stdout.write(out_text)
473
+ sys.stdout.write("\n")
474
+
475
+
476
+ if __name__ == "__main__":
477
+ main()
@@ -81,14 +81,14 @@ AI 助手在处理页面生成需求时,必须按以下流程使用工具:
81
81
  ### 步骤3.5:Sketch 文件解析(如有 .sketch 设计稿)
82
82
  - **当用户提供 .sketch 文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`
83
83
  - 工作流(4步直传):
84
- 1. **解析 Sketch 结构**:`loadSketchByPath` → `listPages` → `listNodesByPage(type="artboard")` → `getNodeInfo`/`getPageStructure(maxDepth=2)` 提取结构
84
+ 1. **解析 Sketch 结构**:`loadSketchByPath` → `listPages` → `queryNodes(pageId, type="artboard")` → `getNodeInfo`/`getPageStructure(maxDepth=2)` 提取结构
85
85
  2. **获取组件库架构**(复用步骤1结果):`get_architecture_overview` 的 `templatePatterns` 提供模板组合模式,`llmMappingHints` 提供常见错误修正
86
86
  3. **LLM 推理组件范围**:从 Sketch 结构化数据推断画板名称→`pageName`、区域分布→`layoutType`、图层类型→组件映射,对照 `templatePatterns` 选择最匹配的模板,输出组件候选列表
87
87
  4. **批量获取组件详情**:调用 `get_component_full_profile(name="Query,TableOrList,LeftTree,TemplateMode", depth="deep")` 一次性获取所有需要的组件 API
88
88
 
89
89
  **Sketch → szcd 工作流(结构化直传,无需视觉模型)**:
90
90
  ```
91
- .sketch → loadSketchByPath → listPages → listNodesByPage(type=artboard)
91
+ .sketch → loadSketchByPath → listPages → queryNodes(pageId, type=artboard)
92
92
  → getNodeInfo/getPageStructure → [步骤1架构数据] → LLM推理组件
93
93
  → get_component_full_profile(批量) → 编码
94
94
  ```
@@ -107,7 +107,7 @@ AI 助手在处理页面生成需求时,必须按以下流程使用工具:
107
107
 
108
108
  **提示**:
109
109
  - 大型 .sketch 文件用 `getPageStructure(maxDepth=1-2)` 即可获取布局概况,避免深层递归超时
110
- - `listNodesByPage(type="artboard")` 优先于 `getPageStructure`,更轻量
110
+ - `queryNodes(pageId, type="artboard")` 优先于 `getPageStructure`,更轻量
111
111
  - 仅当结构化数据不足以判断视觉细节(颜色、间距、字体)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
112
112
  - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
113
113
 
@@ -124,7 +124,7 @@ AI 助手在处理页面生成需求时,必须按以下流程使用工具:
124
124
  - 若用户表示不准确或不采纳,收集原因
125
125
  - 调用 `submit_feedback` 将反馈提交到服务器
126
126
 
127
- ## 可用工具(13 + 17 Sketch)
127
+ ## 可用工具(13 + 15 Sketch)
128
128
 
129
129
  ### Sketch 文件解析工具(sketch-mcp-server,独立 MCP Server)
130
130
 
@@ -152,26 +152,28 @@ sketch-mcp-server 是社区版 MCP Server(npm: `sketch-mcp-server`),通过
152
152
  - `nodeId` (string, required): 节点 ID(artboard 级别效果最佳)
153
153
  - `format` (enum, optional, default: "svg"): 输出格式,当前仅支持 svg
154
154
 
155
- #### S5. getNodesSummary
155
+ #### S5. getPageStructure (mode="summary")
156
156
  **功能**:获取节点统计摘要,Token 消耗比完整信息减少 80-90%。
157
157
  **参数**:
158
- - `pageId` (string, optional): 页面 ID
158
+ - `pageId` (string, required): 页面 ID
159
+ - `mode` (string, required): "summary"
159
160
  - `groupBy` (enum, optional): type/style/position/size
160
- - `includeStats` (boolean, optional, default: true)
161
+ - `maxSamples` (number, optional, default: 5)
161
162
 
162
- #### S6. findNodesByName
163
+ #### S6. queryNodes (name 参数)
163
164
  **功能**:按名称搜索节点,支持模糊匹配。
164
165
  **参数**:
165
166
  - `name` (string, required): 搜索关键词
166
- - `limit` (number, optional, default: 10): 最大返回数量
167
+ - `limit` (number, optional, default: 50): 最大返回数量
167
168
 
168
- #### S7-S17. 其他 Sketch 工具
169
- - `listNodes` / `listNodesByPage` — 列出节点(含过滤)
169
+ #### S7-S15. 其他 Sketch 工具
170
+ - `queryNodes` 统一节点查询(支持 pageId/type/name/nameContains/limit/offset)
170
171
  - `getNodeInfo` / `getMultipleNodeInfo` — 获取节点详情
171
- - `getNodePosition` — 获取节点位置
172
172
  - `getDocumentStructure` — 获取文档结构(支持字段过滤和摘要模式)
173
- - `getSymbolMasters` / `getSymbolInstances` — Symbol 组件管理
173
+ - `getSymbols` — Symbol 组件查询(type="master" 或 "instance")
174
174
  - `getSymbolMasterBySymbolID` / `getSymbolInstanceStyles` — Symbol 详情
175
+ - `getShapePathData` — 提取 shapePath SVG 路径数据
176
+ - `extractBitmaps` / `matchIconFromName` — 资源提取与图标匹配
175
177
 
176
178
  ### 复合组件工具
177
179
 
@@ -209,16 +211,19 @@ sketch-mcp-server 是社区版 MCP Server(npm: `sketch-mcp-server`),通过
209
211
  ### 设计稿分析工具
210
212
 
211
213
  #### 5. analyze_design_image
212
- **功能**:分析 UI 设计稿图片,提取 Token 配置、CSS 覆盖样式、视觉细节。支持 base64/URL/文件路径,pixel_perfect 模式最高还原度。
214
+ **功能**:分析 UI 设计稿图片,提取 Token 配置、CSS 覆盖样式、视觉细节。支持 upload_id/base64/URL/文件路径,pixel_perfect 模式最高还原度。
213
215
  **参数**:
214
- - `imageSource` (string, required): 图片数据(base64/URL/文件路径)
215
- - `sourceType` (enum, optional, default: "file_path"): base64/url/file_path
216
+ - `imageSource` (string, required): 图片数据(upload_id/base64/URL/文件路径)
217
+ - `sourceType` (enum, optional, default: "upload_id"): upload_id/base64/url/file_path
216
218
  - `analysisType` (enum, optional, default: "full"): layout/components/tokens/full/pixel_perfect
217
219
  - `pageContext` (string, optional): 页面业务上下文
218
220
  - `targetLibrary` (enum, optional, default: "auto"): szcd/antd/pro-components/auto
219
221
  - `outputFormat` (enum, optional, default: "markdown"): markdown/json/structured_text/restoration_code
220
222
 
221
- **重要提示**:远程 SSE/HTTP 连接时优先使用 base64;本地 stdio 可用 file_path。
223
+ **重要提示**:
224
+ - 远程 SSE/HTTP 连接时优先使用 upload_id:先调用 `get_upload_endpoint` 获取上传地址,用 curl 上传图片获取 upload_id,再传给本工具
225
+ - **上传重试限制**:curl 上传最多重试 3 次(含首次),超过后停止重试并向用户说明失败原因,不要无限重试浪费 token
226
+ - 本地 stdio 直连可用 file_path
222
227
 
223
228
  ### 样式注入指南工具
224
229