@josephyan/qingflow-cli 0.2.0-beta.984 → 0.2.0-beta.986

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 (43) hide show
  1. package/README.md +2 -2
  2. package/docs/local-agent-install.md +70 -11
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/src/qingflow_mcp/__init__.py +1 -1
  6. package/src/qingflow_mcp/builder_facade/service.py +47 -21
  7. package/src/qingflow_mcp/cli/commands/auth.py +14 -43
  8. package/src/qingflow_mcp/cli/commands/task.py +4 -1
  9. package/src/qingflow_mcp/cli/commands/workspace.py +0 -8
  10. package/src/qingflow_mcp/cli/formatters.py +0 -21
  11. package/src/qingflow_mcp/config.py +39 -0
  12. package/src/qingflow_mcp/errors.py +2 -2
  13. package/src/qingflow_mcp/public_surface.py +2 -6
  14. package/src/qingflow_mcp/response_trim.py +1 -8
  15. package/src/qingflow_mcp/server.py +1 -1
  16. package/src/qingflow_mcp/server_app_builder.py +4 -28
  17. package/src/qingflow_mcp/server_app_user.py +4 -28
  18. package/src/qingflow_mcp/session_store.py +31 -5
  19. package/src/qingflow_mcp/tools/ai_builder_tools.py +117 -1
  20. package/src/qingflow_mcp/tools/app_tools.py +51 -1
  21. package/src/qingflow_mcp/tools/approval_tools.py +82 -1
  22. package/src/qingflow_mcp/tools/auth_tools.py +258 -288
  23. package/src/qingflow_mcp/tools/base.py +204 -4
  24. package/src/qingflow_mcp/tools/code_block_tools.py +21 -0
  25. package/src/qingflow_mcp/tools/custom_button_tools.py +24 -1
  26. package/src/qingflow_mcp/tools/directory_tools.py +28 -1
  27. package/src/qingflow_mcp/tools/feedback_tools.py +8 -0
  28. package/src/qingflow_mcp/tools/file_tools.py +25 -1
  29. package/src/qingflow_mcp/tools/import_tools.py +40 -1
  30. package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
  31. package/src/qingflow_mcp/tools/package_tools.py +37 -1
  32. package/src/qingflow_mcp/tools/portal_tools.py +28 -1
  33. package/src/qingflow_mcp/tools/qingbi_report_tools.py +38 -1
  34. package/src/qingflow_mcp/tools/record_tools.py +255 -2
  35. package/src/qingflow_mcp/tools/repository_dev_tools.py +21 -2
  36. package/src/qingflow_mcp/tools/resource_read_tools.py +23 -1
  37. package/src/qingflow_mcp/tools/role_tools.py +19 -1
  38. package/src/qingflow_mcp/tools/solution_tools.py +56 -1
  39. package/src/qingflow_mcp/tools/task_context_tools.py +205 -6
  40. package/src/qingflow_mcp/tools/task_tools.py +49 -3
  41. package/src/qingflow_mcp/tools/view_tools.py +56 -1
  42. package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
  43. package/src/qingflow_mcp/tools/workspace_tools.py +14 -225
@@ -14,7 +14,7 @@ from mcp.server.fastmcp import FastMCP
14
14
  from ..config import DEFAULT_PROFILE
15
15
  from ..errors import QingflowApiError, raise_tool_error
16
16
  from ..json_types import JSONObject
17
- from .base import ToolBase
17
+ from .base import ToolBase, tool_cn_name
18
18
 
19
19
 
20
20
  ATTACHMENT_UPLOAD_INFO_FALLBACK_CODES = {"40118", 40118}
@@ -22,7 +22,17 @@ LEGACY_OSS_FORM_REQUIRED_KEYS = ("key", "policy", "signature", "ossAccessKeyId")
22
22
 
23
23
 
24
24
  class FileTools(ToolBase):
25
+ """文件工具(中文名:上传与下载中转)。
26
+
27
+ 类型:文件传输工具。
28
+ 主要职责:
29
+ 1. 申请上传凭证与上传地址;
30
+ 2. 支持本地路径、URL、Base64 内容三类上传输入;
31
+ 3. 返回标准化文件元信息供导入与记录附件使用。
32
+ """
33
+
25
34
  def register(self, mcp: FastMCP) -> None:
35
+ """注册当前工具到 MCP 服务。"""
26
36
  @mcp.tool()
27
37
  def file_get_upload_info(
28
38
  profile: str = DEFAULT_PROFILE,
@@ -69,6 +79,7 @@ class FileTools(ToolBase):
69
79
  file_related_url=file_related_url,
70
80
  )
71
81
 
82
+ @tool_cn_name("文件上传信息")
72
83
  def file_get_upload_info(
73
84
  self,
74
85
  *,
@@ -82,6 +93,7 @@ class FileTools(ToolBase):
82
93
  path_id: int | None = None,
83
94
  file_related_url: str | None = None,
84
95
  ) -> dict[str, Any]:
96
+ """执行文件处理相关逻辑。"""
85
97
  def runner(session_profile, context):
86
98
  upload_info = self._request_upload_info_with_fallback(
87
99
  context,
@@ -110,6 +122,7 @@ class FileTools(ToolBase):
110
122
 
111
123
  return self._run(profile, runner)
112
124
 
125
+ @tool_cn_name("本地文件上传")
113
126
  def file_upload_local(
114
127
  self,
115
128
  *,
@@ -122,6 +135,7 @@ class FileTools(ToolBase):
122
135
  path_id: int | None = None,
123
136
  file_related_url: str | None = None,
124
137
  ) -> dict[str, Any]:
138
+ """执行文件处理相关逻辑。"""
125
139
  path = Path(file_path).expanduser()
126
140
  if not path.is_file():
127
141
  raise_tool_error(QingflowApiError.config_error("file_path must point to an existing file"))
@@ -202,6 +216,7 @@ class FileTools(ToolBase):
202
216
  path_id: int | None,
203
217
  file_related_url: str | None,
204
218
  ) -> dict[str, Any]:
219
+ """执行内部辅助逻辑。"""
205
220
  if not file_name:
206
221
  raise_tool_error(QingflowApiError.config_error("file_name is required"))
207
222
  if file_size <= 0:
@@ -225,6 +240,7 @@ class FileTools(ToolBase):
225
240
  return payload
226
241
 
227
242
  def _resolve_upload_endpoint(self, upload_kind: str) -> str:
243
+ """执行内部辅助逻辑。"""
228
244
  normalized = upload_kind.strip().lower()
229
245
  if normalized == "attachment":
230
246
  return "/upload/puburl"
@@ -236,10 +252,12 @@ class FileTools(ToolBase):
236
252
  raise AssertionError("unreachable")
237
253
 
238
254
  def _encode_formula(self, formula: str) -> str:
255
+ """执行内部辅助逻辑。"""
239
256
  encoded = base64.b64encode(quote(formula, safe="").encode("utf-8")).decode("utf-8")
240
257
  return f"{self._random_string(16)}{encoded}{self._random_string(16)}"
241
258
 
242
259
  def _random_string(self, length: int) -> str:
260
+ """执行内部辅助逻辑。"""
243
261
  alphabet = string.ascii_letters + string.digits
244
262
  return "".join(random.choice(alphabet) for _ in range(length))
245
263
 
@@ -256,6 +274,7 @@ class FileTools(ToolBase):
256
274
  path_id: int | None,
257
275
  file_related_url: str | None,
258
276
  ) -> dict[str, Any]:
277
+ """执行内部辅助逻辑。"""
259
278
  requested_kind = upload_kind.strip().lower()
260
279
  attempted_kinds = [requested_kind]
261
280
  if requested_kind == "attachment":
@@ -305,6 +324,7 @@ class FileTools(ToolBase):
305
324
  path_id: int | None,
306
325
  file_related_url: str | None,
307
326
  ) -> JSONObject:
327
+ """执行内部辅助逻辑。"""
308
328
  endpoint = self._resolve_upload_endpoint(upload_kind)
309
329
  file_upload_bo = self._build_file_upload_bo(
310
330
  upload_kind=upload_kind,
@@ -336,6 +356,7 @@ class FileTools(ToolBase):
336
356
  attempted_kind: str,
337
357
  error: QingflowApiError,
338
358
  ) -> bool:
359
+ """执行内部辅助逻辑。"""
339
360
  return (
340
361
  requested_kind == "attachment"
341
362
  and attempted_kind == "attachment"
@@ -343,6 +364,7 @@ class FileTools(ToolBase):
343
364
  )
344
365
 
345
366
  def _is_legacy_oss_form_upload(self, payload: JSONObject) -> bool:
367
+ """执行内部辅助逻辑。"""
346
368
  return all(str(payload.get(key) or "").strip() for key in LEGACY_OSS_FORM_REQUIRED_KEYS)
347
369
 
348
370
  def _upload_legacy_oss_form(
@@ -353,6 +375,7 @@ class FileTools(ToolBase):
353
375
  content: bytes,
354
376
  content_type: str,
355
377
  ) -> dict[str, Any]:
378
+ """执行内部辅助逻辑。"""
356
379
  upload_url = self._resolve_legacy_oss_form_url(upload_info)
357
380
  if not upload_url:
358
381
  raise QingflowApiError.config_error("legacy upload payload is missing host/uploadAccelerateHost")
@@ -375,6 +398,7 @@ class FileTools(ToolBase):
375
398
  )
376
399
 
377
400
  def _resolve_legacy_oss_form_url(self, upload_info: JSONObject) -> str:
401
+ """执行内部辅助逻辑。"""
378
402
  for key in ("uploadAccelerateHost", "host", "uploadUrl"):
379
403
  value = str(upload_info.get(key) or "").strip()
380
404
  if not value:
@@ -21,7 +21,7 @@ from ..errors import QingflowApiError
21
21
  from ..import_store import ImportJobStore, ImportVerificationStore
22
22
  from ..json_types import JSONObject
23
23
  from .app_tools import _derive_import_capability
24
- from .base import ToolBase
24
+ from .base import ToolBase, tool_cn_name
25
25
  from .file_tools import FileTools
26
26
  from .record_tools import RecordTools, _build_field_index, _normalize_form_schema
27
27
 
@@ -40,6 +40,15 @@ EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
40
40
 
41
41
 
42
42
  class ImportTools(ToolBase):
43
+ """导入工具(中文名:数据导入与校验)。
44
+
45
+ 类型:批量数据导入工具。
46
+ 主要职责:
47
+ 1. 获取导入模板与导入 schema;
48
+ 2. 执行导入文件校验与本地修复;
49
+ 3. 启动导入任务并查询导入进度与结果。
50
+ """
51
+
43
52
  def __init__(
44
53
  self,
45
54
  sessions,
@@ -48,6 +57,7 @@ class ImportTools(ToolBase):
48
57
  verification_store: ImportVerificationStore | None = None,
49
58
  job_store: ImportJobStore | None = None,
50
59
  ) -> None:
60
+ """执行内部辅助逻辑。"""
51
61
  super().__init__(sessions, backend)
52
62
  self._record_tools = RecordTools(sessions, backend)
53
63
  self._file_tools = FileTools(sessions, backend)
@@ -55,6 +65,7 @@ class ImportTools(ToolBase):
55
65
  self._job_store = job_store or ImportJobStore()
56
66
 
57
67
  def register(self, mcp: FastMCP) -> None:
68
+ """注册当前工具到 MCP 服务。"""
58
69
  @mcp.tool()
59
70
  def record_import_schema_get(
60
71
  app_key: str = "",
@@ -155,6 +166,7 @@ class ImportTools(ToolBase):
155
166
  process_id_str=process_id_str,
156
167
  )
157
168
 
169
+ @tool_cn_name("导入 Schema")
158
170
  def record_import_schema_get(
159
171
  self,
160
172
  *,
@@ -162,6 +174,7 @@ class ImportTools(ToolBase):
162
174
  app_key: str,
163
175
  output_profile: str = "normal",
164
176
  ) -> dict[str, Any]:
177
+ """执行记录相关逻辑。"""
165
178
  if not app_key.strip():
166
179
  return {
167
180
  "ok": False,
@@ -217,6 +230,7 @@ class ImportTools(ToolBase):
217
230
 
218
231
  return self._run(profile, runner)
219
232
 
233
+ @tool_cn_name("导入模板")
220
234
  def record_import_template_get(
221
235
  self,
222
236
  *,
@@ -224,6 +238,7 @@ class ImportTools(ToolBase):
224
238
  app_key: str,
225
239
  download_to_path: str | None = None,
226
240
  ) -> dict[str, Any]:
241
+ """执行记录相关逻辑。"""
227
242
  if not app_key.strip():
228
243
  return self._failed_template_result(app_key=app_key, error_code="IMPORT_TEMPLATE_UNAUTHORIZED", message="app_key is required")
229
244
 
@@ -316,6 +331,7 @@ class ImportTools(ToolBase):
316
331
  except RuntimeError as exc:
317
332
  return self._runtime_error_as_result(exc, error_code="IMPORT_TEMPLATE_UNAUTHORIZED")
318
333
 
334
+ @tool_cn_name("导入校验")
319
335
  def record_import_verify(
320
336
  self,
321
337
  *,
@@ -323,6 +339,7 @@ class ImportTools(ToolBase):
323
339
  app_key: str,
324
340
  file_path: str,
325
341
  ) -> dict[str, Any]:
342
+ """执行记录相关逻辑。"""
326
343
  if not app_key.strip():
327
344
  return self._failed_verify_result(app_key=app_key, file_path=file_path, error_code="IMPORT_VERIFICATION_FAILED", message="app_key is required")
328
345
  path = Path(file_path).expanduser()
@@ -531,6 +548,7 @@ class ImportTools(ToolBase):
531
548
  except RuntimeError as exc:
532
549
  return self._runtime_error_as_result(exc, error_code="IMPORT_VERIFICATION_FAILED", extra={"can_import": False})
533
550
 
551
+ @tool_cn_name("导入修复")
534
552
  def record_import_repair_local(
535
553
  self,
536
554
  *,
@@ -540,6 +558,7 @@ class ImportTools(ToolBase):
540
558
  output_path: str | None = None,
541
559
  selected_repairs: list[str] | None = None,
542
560
  ) -> dict[str, Any]:
561
+ """执行记录相关逻辑。"""
543
562
  if not verification_id.strip():
544
563
  return self._failed_repair_result(error_code="IMPORT_VERIFICATION_FAILED", message="verification_id is required")
545
564
  if not authorized_file_modification:
@@ -639,6 +658,7 @@ class ImportTools(ToolBase):
639
658
  except RuntimeError as exc:
640
659
  return self._runtime_error_as_result(exc, error_code="IMPORT_REPAIR_FORMAT_UNSUPPORTED")
641
660
 
661
+ @tool_cn_name("开始导入")
642
662
  def record_import_start(
643
663
  self,
644
664
  *,
@@ -648,6 +668,7 @@ class ImportTools(ToolBase):
648
668
  being_enter_auditing: bool | None,
649
669
  view_key: str | None = None,
650
670
  ) -> dict[str, Any]:
671
+ """执行记录相关逻辑。"""
651
672
  if being_enter_auditing is None:
652
673
  return self._failed_start_result(error_code="IMPORT_VERIFICATION_FAILED", message="being_enter_auditing must be passed explicitly")
653
674
 
@@ -744,6 +765,7 @@ class ImportTools(ToolBase):
744
765
  except RuntimeError as exc:
745
766
  return self._runtime_error_as_result(exc, error_code="IMPORT_VERIFICATION_FAILED", extra={"accepted": False})
746
767
 
768
+ @tool_cn_name("导入状态")
747
769
  def record_import_status_get(
748
770
  self,
749
771
  *,
@@ -752,6 +774,7 @@ class ImportTools(ToolBase):
752
774
  import_id: str | None = None,
753
775
  process_id_str: str | None = None,
754
776
  ) -> dict[str, Any]:
777
+ """执行记录相关逻辑。"""
755
778
  normalized_app_key = (app_key or "").strip()
756
779
  normalized_import_id = _normalize_optional_text(import_id)
757
780
  normalized_process_id = _normalize_optional_text(process_id_str)
@@ -883,6 +906,7 @@ class ImportTools(ToolBase):
883
906
  *,
884
907
  import_capability: JSONObject | None = None,
885
908
  ) -> tuple[Any, list[JSONObject], str]: # type: ignore[no-untyped-def]
909
+ """执行内部辅助逻辑。"""
886
910
  auth_source = _normalize_optional_text((import_capability or {}).get("auth_source")) or "unknown"
887
911
  if auth_source == "data_manage_auth":
888
912
  schema = self.backend.request("GET", context, f"/app/{app_key}/form", params={"type": 1})
@@ -929,6 +953,7 @@ class ImportTools(ToolBase):
929
953
  *,
930
954
  import_capability: JSONObject | None = None,
931
955
  ) -> tuple[list[JSONObject], str]: # type: ignore[no-untyped-def]
956
+ """执行内部辅助逻辑。"""
932
957
  _, expected_columns, schema_fingerprint = self._resolve_import_schema_bundle(
933
958
  profile,
934
959
  context,
@@ -949,6 +974,7 @@ class ImportTools(ToolBase):
949
974
  allowed_header_titles: list[str] | None,
950
975
  schema_fingerprint: str,
951
976
  ) -> dict[str, Any]:
977
+ """执行内部辅助逻辑。"""
952
978
  extension = path.suffix.lower()
953
979
  file_sha256 = _sha256_file(path)
954
980
  base_result = {
@@ -1065,6 +1091,7 @@ class ImportTools(ToolBase):
1065
1091
  expected_columns: list[JSONObject],
1066
1092
  field_index: Any,
1067
1093
  ) -> tuple[list[JSONObject], list[JSONObject]]: # type: ignore[no-untyped-def]
1094
+ """执行内部辅助逻辑。"""
1068
1095
  issues: list[JSONObject] = []
1069
1096
  warnings: list[JSONObject] = []
1070
1097
  header_positions = _sheet_header_positions(sheet)
@@ -1144,6 +1171,7 @@ class ImportTools(ToolBase):
1144
1171
  column: JSONObject,
1145
1172
  field,
1146
1173
  ) -> tuple[JSONObject | None, JSONObject | None]: # type: ignore[no-untyped-def]
1174
+ """执行内部辅助逻辑。"""
1147
1175
  invalid_email_samples: list[str] = []
1148
1176
  scope_miss_samples: list[str] = []
1149
1177
  checked_values: set[str] = set()
@@ -1202,6 +1230,7 @@ class ImportTools(ToolBase):
1202
1230
  column: JSONObject,
1203
1231
  field,
1204
1232
  ) -> tuple[JSONObject | None, JSONObject | None]: # type: ignore[no-untyped-def]
1233
+ """执行内部辅助逻辑。"""
1205
1234
  scope_miss_samples: list[str] = []
1206
1235
  checked_values: set[str] = set()
1207
1236
  for row_index in range(2, sheet.max_row + 1):
@@ -1248,6 +1277,7 @@ class ImportTools(ToolBase):
1248
1277
  import_capability: JSONObject | None = None,
1249
1278
  expected_columns: list[JSONObject] | None = None,
1250
1279
  ) -> tuple[dict[str, Any], list[JSONObject]]: # type: ignore[no-untyped-def]
1280
+ """执行内部辅助逻辑。"""
1251
1281
  warnings: list[JSONObject] = []
1252
1282
  try:
1253
1283
  payload = self.backend.request("GET", context, f"/app/{app_key}/apply/excelTemplate")
@@ -1298,6 +1328,7 @@ class ImportTools(ToolBase):
1298
1328
  template_header_profile: dict[str, Any],
1299
1329
  local_check: dict[str, Any],
1300
1330
  ) -> dict[str, Any] | None:
1331
+ """执行内部辅助逻辑。"""
1301
1332
  if source_path.suffix.lower() != ".xlsx":
1302
1333
  return None
1303
1334
  try:
@@ -1345,6 +1376,7 @@ class ImportTools(ToolBase):
1345
1376
  return normalized
1346
1377
 
1347
1378
  def _fetch_import_capability(self, context, app_key: str) -> tuple[JSONObject, list[JSONObject]]: # type: ignore[no-untyped-def]
1379
+ """执行内部辅助逻辑。"""
1348
1380
  try:
1349
1381
  payload = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
1350
1382
  except QingflowApiError:
@@ -1358,6 +1390,7 @@ class ImportTools(ToolBase):
1358
1390
  destination_hint: str | None,
1359
1391
  app_key: str,
1360
1392
  ) -> str:
1393
+ """执行内部辅助逻辑。"""
1361
1394
  if destination_hint:
1362
1395
  destination = _resolve_template_download_path(destination_hint, app_key=app_key)
1363
1396
  else:
@@ -1378,6 +1411,7 @@ class ImportTools(ToolBase):
1378
1411
  message: str,
1379
1412
  request_route: JSONObject | None = None,
1380
1413
  ) -> dict[str, Any]:
1414
+ """执行内部辅助逻辑。"""
1381
1415
  return {
1382
1416
  "ok": False,
1383
1417
  "status": "failed",
@@ -1402,6 +1436,7 @@ class ImportTools(ToolBase):
1402
1436
  message: str,
1403
1437
  extra: dict[str, Any] | None = None,
1404
1438
  ) -> dict[str, Any]:
1439
+ """执行内部辅助逻辑。"""
1405
1440
  payload = {
1406
1441
  "ok": True,
1407
1442
  "status": "failed",
@@ -1434,6 +1469,7 @@ class ImportTools(ToolBase):
1434
1469
  return payload
1435
1470
 
1436
1471
  def _failed_repair_result(self, *, error_code: str, message: str, extra: dict[str, Any] | None = None) -> dict[str, Any]:
1472
+ """执行内部辅助逻辑。"""
1437
1473
  payload = {
1438
1474
  "ok": False,
1439
1475
  "status": "failed",
@@ -1457,6 +1493,7 @@ class ImportTools(ToolBase):
1457
1493
  return payload
1458
1494
 
1459
1495
  def _failed_start_result(self, *, error_code: str, message: str, extra: dict[str, Any] | None = None) -> dict[str, Any]:
1496
+ """执行内部辅助逻辑。"""
1460
1497
  payload = {
1461
1498
  "ok": False,
1462
1499
  "status": "failed",
@@ -1481,6 +1518,7 @@ class ImportTools(ToolBase):
1481
1518
  return payload
1482
1519
 
1483
1520
  def _failed_status_result(self, *, error_code: str, message: str, extra: dict[str, Any] | None = None) -> dict[str, Any]:
1521
+ """执行内部辅助逻辑。"""
1484
1522
  payload = {
1485
1523
  "ok": False,
1486
1524
  "status": "failed",
@@ -1514,6 +1552,7 @@ class ImportTools(ToolBase):
1514
1552
  error_code: str,
1515
1553
  extra: dict[str, Any] | None = None,
1516
1554
  ) -> dict[str, Any]:
1555
+ """执行内部辅助逻辑。"""
1517
1556
  try:
1518
1557
  payload = json.loads(str(error))
1519
1558
  except json.JSONDecodeError:
@@ -5,11 +5,21 @@ from mcp.server.fastmcp import FastMCP
5
5
  from ..config import DEFAULT_PROFILE
6
6
  from ..errors import QingflowApiError, raise_tool_error
7
7
  from ..json_types import JSONObject
8
- from .base import ToolBase
8
+ from .base import ToolBase, tool_cn_name
9
9
 
10
10
 
11
11
  class NavigationTools(ToolBase):
12
+ """导航工具(中文名:导航菜单管理)。
13
+
14
+ 类型:门户导航配置工具。
15
+ 主要职责:
16
+ 1. 查询导航结构与发布状态;
17
+ 2. 读取指定导航详情;
18
+ 3. 应用导航配置变更。
19
+ """
20
+
12
21
  def register(self, mcp: FastMCP) -> None:
22
+ """注册当前工具到 MCP 服务。"""
13
23
  @mcp.tool()
14
24
  def navigation_list_published(profile: str = DEFAULT_PROFILE, page_num: int = 1, page_size: int = 50) -> JSONObject:
15
25
  return self.navigation_list_published(profile=profile, page_num=page_num, page_size=page_size)
@@ -59,14 +69,18 @@ class NavigationTools(ToolBase):
59
69
  def navigation_reorder(profile: str = DEFAULT_PROFILE, payload: list[JSONObject] | None = None) -> JSONObject:
60
70
  return self.navigation_reorder(profile=profile, payload=payload or [])
61
71
 
72
+ @tool_cn_name("导航已发布列表")
62
73
  def navigation_list_published(self, *, profile: str, page_num: int = 1, page_size: int = 50) -> JSONObject:
74
+ """执行工具方法逻辑。"""
63
75
  def runner(session_profile, context):
64
76
  result = self.backend.request("GET", context, "/navigation", params={"pageNum": page_num, "pageSize": page_size})
65
77
  return {"profile": profile, "ws_id": session_profile.selected_ws_id, "page": result}
66
78
 
67
79
  return self._run(profile, runner)
68
80
 
81
+ @tool_cn_name("导航草稿分页")
69
82
  def navigation_list_draft_page(self, *, profile: str, page_num: int = 1, page_size: int = 50, query_key: str | None = None) -> JSONObject:
83
+ """执行工具方法逻辑。"""
70
84
  def runner(session_profile, context):
71
85
  params: JSONObject = {"pageNum": page_num, "pageSize": page_size}
72
86
  if query_key:
@@ -76,7 +90,9 @@ class NavigationTools(ToolBase):
76
90
 
77
91
  return self._run(profile, runner)
78
92
 
93
+ @tool_cn_name("导航草稿全量")
79
94
  def navigation_list_draft_all(self, *, profile: str, query_key: str | None = None) -> JSONObject:
95
+ """执行工具方法逻辑。"""
80
96
  def runner(session_profile, context):
81
97
  params: JSONObject = {}
82
98
  if query_key:
@@ -86,7 +102,9 @@ class NavigationTools(ToolBase):
86
102
 
87
103
  return self._run(profile, runner)
88
104
 
105
+ @tool_cn_name("导航详情")
89
106
  def navigation_get_detail(self, *, profile: str, navigation_item_id: int, status: str = "draft") -> JSONObject:
107
+ """执行工具方法逻辑。"""
90
108
  self._require_navigation_item_id(navigation_item_id)
91
109
 
92
110
  def runner(session_profile, context):
@@ -100,14 +118,18 @@ class NavigationTools(ToolBase):
100
118
 
101
119
  return self._run(profile, runner)
102
120
 
121
+ @tool_cn_name("导航发布状态")
103
122
  def navigation_get_status(self, *, profile: str) -> JSONObject:
123
+ """执行工具方法逻辑。"""
104
124
  def runner(session_profile, context):
105
125
  result = self.backend.request("GET", context, "/navigation/status")
106
126
  return {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
107
127
 
108
128
  return self._run(profile, runner)
109
129
 
130
+ @tool_cn_name("设置导航可见性")
110
131
  def navigation_set_visible(self, *, profile: str, payload: JSONObject) -> JSONObject:
132
+ """执行工具方法逻辑。"""
111
133
  body = self._require_dict(payload)
112
134
 
113
135
  def runner(session_profile, context):
@@ -116,7 +138,9 @@ class NavigationTools(ToolBase):
116
138
 
117
139
  return self._run(profile, runner)
118
140
 
141
+ @tool_cn_name("创建导航项")
119
142
  def navigation_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
143
+ """执行工具方法逻辑。"""
120
144
  body = self._require_dict(payload)
121
145
 
122
146
  def runner(session_profile, context):
@@ -125,7 +149,9 @@ class NavigationTools(ToolBase):
125
149
 
126
150
  return self._run(profile, runner)
127
151
 
152
+ @tool_cn_name("更新导航项")
128
153
  def navigation_update(self, *, profile: str, navigation_item_id: int, payload: JSONObject) -> JSONObject:
154
+ """执行工具方法逻辑。"""
129
155
  self._require_navigation_item_id(navigation_item_id)
130
156
  body = self._require_dict(payload)
131
157
 
@@ -139,7 +165,9 @@ class NavigationTools(ToolBase):
139
165
 
140
166
  return self._run(profile, runner)
141
167
 
168
+ @tool_cn_name("删除导航项")
142
169
  def navigation_delete(self, *, profile: str, navigation_item_id: int) -> JSONObject:
170
+ """执行工具方法逻辑。"""
143
171
  self._require_navigation_item_id(navigation_item_id)
144
172
 
145
173
  def runner(session_profile, context):
@@ -152,7 +180,9 @@ class NavigationTools(ToolBase):
152
180
 
153
181
  return self._run(profile, runner)
154
182
 
183
+ @tool_cn_name("发布导航")
155
184
  def navigation_publish(self, *, profile: str, navigation_id: int) -> JSONObject:
185
+ """执行工具方法逻辑。"""
156
186
  if navigation_id <= 0:
157
187
  raise_tool_error(QingflowApiError.config_error("navigation_id must be positive"))
158
188
 
@@ -162,7 +192,9 @@ class NavigationTools(ToolBase):
162
192
 
163
193
  return self._run(profile, runner)
164
194
 
195
+ @tool_cn_name("导航排序")
165
196
  def navigation_reorder(self, *, profile: str, payload: list[JSONObject]) -> JSONObject:
197
+ """执行工具方法逻辑。"""
166
198
  if not isinstance(payload, list) or not payload:
167
199
  raise_tool_error(QingflowApiError.config_error("payload must be a non-empty array"))
168
200
 
@@ -173,5 +205,6 @@ class NavigationTools(ToolBase):
173
205
  return self._run(profile, runner)
174
206
 
175
207
  def _require_navigation_item_id(self, navigation_item_id: int) -> None:
208
+ """执行内部辅助逻辑。"""
176
209
  if navigation_item_id <= 0:
177
210
  raise_tool_error(QingflowApiError.config_error("navigation_item_id must be positive"))
@@ -7,11 +7,21 @@ from mcp.server.fastmcp import FastMCP
7
7
  from ..config import DEFAULT_PROFILE
8
8
  from ..errors import QingflowApiError, raise_tool_error
9
9
  from ..json_types import JSONObject
10
- from .base import ToolBase
10
+ from .base import ToolBase, tool_cn_name
11
11
 
12
12
 
13
13
  class PackageTools(ToolBase):
14
+ """分组工具(中文名:分组与归档管理)。
15
+
16
+ 类型:资源组织工具。
17
+ 主要职责:
18
+ 1. 查询、解析、创建分组;
19
+ 2. 管理应用与分组的挂载关系;
20
+ 3. 为应用搭建提供分组定位能力。
21
+ """
22
+
14
23
  def register(self, mcp: FastMCP) -> None:
24
+ """注册当前工具到 MCP 服务。"""
15
25
  @mcp.tool()
16
26
  def package_list(profile: str = DEFAULT_PROFILE, trial_status: str = "all", include_raw: bool = False) -> JSONObject:
17
27
  return self.package_list(profile=profile, trial_status=trial_status, include_raw=include_raw)
@@ -36,7 +46,9 @@ class PackageTools(ToolBase):
36
46
  def package_delete(profile: str = DEFAULT_PROFILE, tag_id: int = 0, deleted_all_data: bool = False) -> JSONObject:
37
47
  return self.package_delete(profile=profile, tag_id=tag_id, deleted_all_data=deleted_all_data)
38
48
 
49
+ @tool_cn_name("分组列表")
39
50
  def package_list(self, *, profile: str, trial_status: str = "all", include_raw: bool = False) -> JSONObject:
51
+ """执行分组与包相关逻辑。"""
40
52
  def runner(session_profile, context):
41
53
  result = self.backend.request("GET", context, "/tag", params={"trialStatus": trial_status})
42
54
  raw_items, source_shape = self._extract_package_items(result)
@@ -57,7 +69,9 @@ class PackageTools(ToolBase):
57
69
 
58
70
  return self._run(profile, runner)
59
71
 
72
+ @tool_cn_name("分组详情")
60
73
  def package_get(self, *, profile: str, tag_id: int, include_raw: bool = False) -> JSONObject:
74
+ """执行分组与包相关逻辑。"""
61
75
  self._require_tag_id(tag_id)
62
76
 
63
77
  def runner(session_profile, context):
@@ -80,7 +94,9 @@ class PackageTools(ToolBase):
80
94
 
81
95
  return self._run(profile, runner)
82
96
 
97
+ @tool_cn_name("分组基础信息")
83
98
  def package_get_base(self, *, profile: str, tag_id: int, include_raw: bool = False) -> JSONObject:
99
+ """执行分组与包相关逻辑。"""
84
100
  self._require_readable_tag_id(tag_id)
85
101
 
86
102
  def runner(session_profile, context):
@@ -99,7 +115,9 @@ class PackageTools(ToolBase):
99
115
 
100
116
  return self._run(profile, runner)
101
117
 
118
+ @tool_cn_name("创建分组")
102
119
  def package_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
120
+ """执行分组与包相关逻辑。"""
103
121
  body = self._require_dict(payload)
104
122
 
105
123
  def runner(session_profile, context):
@@ -108,7 +126,9 @@ class PackageTools(ToolBase):
108
126
 
109
127
  return self._run(profile, runner)
110
128
 
129
+ @tool_cn_name("更新分组")
111
130
  def package_update(self, *, profile: str, tag_id: int, payload: JSONObject) -> JSONObject:
131
+ """执行分组与包相关逻辑。"""
112
132
  self._require_tag_id(tag_id)
113
133
  body = self._require_dict(payload)
114
134
 
@@ -128,7 +148,9 @@ class PackageTools(ToolBase):
128
148
 
129
149
  return self._run(profile, runner)
130
150
 
151
+ @tool_cn_name("分组排序")
131
152
  def package_sort_items(self, *, profile: str, tag_id: int, tag_items: list[JSONObject]) -> JSONObject:
153
+ """执行分组与包相关逻辑。"""
132
154
  self._require_tag_id(tag_id)
133
155
 
134
156
  def runner(session_profile, context):
@@ -142,7 +164,9 @@ class PackageTools(ToolBase):
142
164
 
143
165
  return self._run(profile, runner)
144
166
 
167
+ @tool_cn_name("创建分组分栏")
145
168
  def package_group_create(self, *, profile: str, tag_id: int, group_name: str) -> JSONObject:
169
+ """执行分组与包相关逻辑。"""
146
170
  self._require_tag_id(tag_id)
147
171
  normalized_name = str(group_name or "").strip()
148
172
  if not normalized_name:
@@ -159,7 +183,9 @@ class PackageTools(ToolBase):
159
183
 
160
184
  return self._run(profile, runner)
161
185
 
186
+ @tool_cn_name("更新分组分栏")
162
187
  def package_group_update(self, *, profile: str, tag_id: int, group_id: int, group_name: str) -> JSONObject:
188
+ """执行分组与包相关逻辑。"""
163
189
  self._require_tag_id(tag_id)
164
190
  self._require_group_id(group_id)
165
191
  normalized_name = str(group_name or "").strip()
@@ -177,7 +203,9 @@ class PackageTools(ToolBase):
177
203
 
178
204
  return self._run(profile, runner)
179
205
 
206
+ @tool_cn_name("删除分组分栏")
180
207
  def package_group_delete(self, *, profile: str, tag_id: int, group_id: int) -> JSONObject:
208
+ """执行分组与包相关逻辑。"""
181
209
  self._require_tag_id(tag_id)
182
210
  self._require_group_id(group_id)
183
211
 
@@ -187,7 +215,9 @@ class PackageTools(ToolBase):
187
215
 
188
216
  return self._run(profile, runner)
189
217
 
218
+ @tool_cn_name("删除分组")
190
219
  def package_delete(self, *, profile: str, tag_id: int, deleted_all_data: bool = False) -> JSONObject:
220
+ """执行分组与包相关逻辑。"""
191
221
  self._require_tag_id(tag_id)
192
222
 
193
223
  def runner(session_profile, context):
@@ -206,18 +236,22 @@ class PackageTools(ToolBase):
206
236
  return self._run(profile, runner)
207
237
 
208
238
  def _require_tag_id(self, tag_id: int) -> None:
239
+ """执行内部辅助逻辑。"""
209
240
  if tag_id <= 0:
210
241
  raise_tool_error(QingflowApiError.config_error("tag_id must be positive"))
211
242
 
212
243
  def _require_group_id(self, group_id: int) -> None:
244
+ """执行内部辅助逻辑。"""
213
245
  if group_id <= 0:
214
246
  raise_tool_error(QingflowApiError.config_error("group_id must be positive"))
215
247
 
216
248
  def _require_readable_tag_id(self, tag_id: int) -> None:
249
+ """执行内部辅助逻辑。"""
217
250
  if tag_id < 0:
218
251
  raise_tool_error(QingflowApiError.config_error("tag_id must be non-negative"))
219
252
 
220
253
  def _normalize_package_payload(self, payload: JSONObject, existing: JSONObject | None = None) -> JSONObject:
254
+ """执行内部辅助逻辑。"""
221
255
  data = deepcopy(existing) if isinstance(existing, dict) else {}
222
256
  data.update(deepcopy(payload))
223
257
  data.pop("tagId", None)
@@ -232,6 +266,7 @@ class PackageTools(ToolBase):
232
266
  return data
233
267
 
234
268
  def _compact_package(self, result: dict[str, object], *, base_info: dict[str, object] | None = None) -> JSONObject:
269
+ """执行内部辅助逻辑。"""
235
270
  tag_items = result.get("tagItems") if isinstance(result.get("tagItems"), list) else []
236
271
  permission_source = base_info if isinstance(base_info, dict) else result
237
272
  preview = []
@@ -262,6 +297,7 @@ class PackageTools(ToolBase):
262
297
  return {key: value for key, value in compact.items() if value is not None}
263
298
 
264
299
  def _extract_package_items(self, result: object) -> tuple[list[dict[str, object]], str]:
300
+ """执行内部辅助逻辑。"""
265
301
  if isinstance(result, list):
266
302
  return [item for item in result if isinstance(item, dict)], "list"
267
303
  if isinstance(result, dict):