@josephyan/qingflow-cli 0.2.0-beta.985 → 0.2.0-beta.987
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 +2 -2
- package/docs/local-agent-install.md +70 -11
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +376 -19
- package/src/qingflow_mcp/cli/commands/auth.py +14 -43
- package/src/qingflow_mcp/cli/commands/workspace.py +8 -5
- package/src/qingflow_mcp/cli/formatters.py +19 -22
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/public_surface.py +4 -6
- package/src/qingflow_mcp/response_trim.py +1 -8
- package/src/qingflow_mcp/server.py +1 -1
- package/src/qingflow_mcp/server_app_builder.py +4 -28
- package/src/qingflow_mcp/server_app_user.py +4 -28
- package/src/qingflow_mcp/session_store.py +31 -5
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
- package/src/qingflow_mcp/solution/executor.py +2 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +117 -1
- package/src/qingflow_mcp/tools/app_tools.py +51 -1
- package/src/qingflow_mcp/tools/approval_tools.py +82 -1
- package/src/qingflow_mcp/tools/auth_tools.py +306 -288
- package/src/qingflow_mcp/tools/base.py +204 -4
- package/src/qingflow_mcp/tools/code_block_tools.py +21 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +24 -1
- package/src/qingflow_mcp/tools/directory_tools.py +28 -1
- package/src/qingflow_mcp/tools/feedback_tools.py +8 -0
- package/src/qingflow_mcp/tools/file_tools.py +25 -1
- package/src/qingflow_mcp/tools/import_tools.py +40 -1
- package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
- package/src/qingflow_mcp/tools/package_tools.py +37 -1
- package/src/qingflow_mcp/tools/portal_tools.py +28 -1
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +38 -1
- package/src/qingflow_mcp/tools/record_tools.py +255 -2
- package/src/qingflow_mcp/tools/repository_dev_tools.py +21 -2
- package/src/qingflow_mcp/tools/resource_read_tools.py +23 -1
- package/src/qingflow_mcp/tools/role_tools.py +19 -1
- package/src/qingflow_mcp/tools/solution_tools.py +56 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +72 -1
- package/src/qingflow_mcp/tools/task_tools.py +49 -3
- package/src/qingflow_mcp/tools/view_tools.py +56 -1
- package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
- package/src/qingflow_mcp/tools/workspace_tools.py +100 -217
|
@@ -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):
|