@qingflow-tech/qingflow-app-user-mcp 1.0.2 → 1.0.4
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 +9 -3
- package/npm/lib/runtime.mjs +10 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +21 -12
- package/skills/qingflow-app-user/references/data-gotchas.md +1 -1
- package/skills/qingflow-app-user/references/public-surface-sync.md +70 -0
- package/skills/qingflow-app-user/references/record-patterns.md +1 -1
- package/skills/qingflow-record-analysis/SKILL.md +44 -2
- package/skills/qingflow-record-insert/SKILL.md +3 -0
- package/skills/qingflow-record-update/SKILL.md +3 -0
- package/skills/qingflow-task-ops/SKILL.md +31 -10
- package/src/qingflow_mcp/__init__.py +33 -1
- package/src/qingflow_mcp/backend_client.py +109 -0
- package/src/qingflow_mcp/builder_facade/button_style_catalog.py +282 -0
- package/src/qingflow_mcp/builder_facade/models.py +58 -9
- package/src/qingflow_mcp/builder_facade/service.py +1711 -240
- package/src/qingflow_mcp/cli/commands/__init__.py +2 -1
- package/src/qingflow_mcp/cli/commands/app.py +47 -1
- package/src/qingflow_mcp/cli/commands/auth.py +63 -0
- package/src/qingflow_mcp/cli/commands/builder.py +11 -3
- package/src/qingflow_mcp/cli/commands/exports.py +111 -0
- package/src/qingflow_mcp/cli/commands/record.py +5 -5
- package/src/qingflow_mcp/cli/commands/task.py +701 -27
- package/src/qingflow_mcp/cli/commands/workspace.py +84 -0
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +424 -50
- package/src/qingflow_mcp/cli/interaction.py +72 -0
- package/src/qingflow_mcp/cli/main.py +11 -1
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +218 -0
- package/src/qingflow_mcp/config.py +1 -1
- package/src/qingflow_mcp/errors.py +4 -4
- package/src/qingflow_mcp/export_store.py +14 -0
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/public_surface.py +16 -1
- package/src/qingflow_mcp/response_trim.py +394 -9
- package/src/qingflow_mcp/server.py +26 -0
- package/src/qingflow_mcp/server_app_builder.py +15 -1
- package/src/qingflow_mcp/server_app_user.py +113 -0
- package/src/qingflow_mcp/session_store.py +126 -21
- 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 +107 -34
- package/src/qingflow_mcp/tools/app_tools.py +1 -0
- package/src/qingflow_mcp/tools/auth_tools.py +243 -9
- package/src/qingflow_mcp/tools/base.py +6 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +2 -2
- package/src/qingflow_mcp/tools/custom_button_tools.py +0 -2
- package/src/qingflow_mcp/tools/export_tools.py +1565 -0
- package/src/qingflow_mcp/tools/import_tools.py +78 -4
- package/src/qingflow_mcp/tools/record_tools.py +551 -165
- package/src/qingflow_mcp/tools/resource_read_tools.py +154 -33
- package/src/qingflow_mcp/tools/task_context_tools.py +917 -141
- package/src/qingflow_mcp/tools/workspace_tools.py +141 -0
|
@@ -37,6 +37,13 @@ SAFE_REPAIRS = {
|
|
|
37
37
|
"normalize_url_cells",
|
|
38
38
|
}
|
|
39
39
|
EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
|
40
|
+
IMPORT_STATUS_BY_PROCESS_STATUS = {
|
|
41
|
+
1: "queued",
|
|
42
|
+
2: "running",
|
|
43
|
+
3: "succeeded",
|
|
44
|
+
4: "failed",
|
|
45
|
+
5: "partially_failed",
|
|
46
|
+
}
|
|
40
47
|
|
|
41
48
|
|
|
42
49
|
class ImportTools(ToolBase):
|
|
@@ -783,6 +790,8 @@ class ImportTools(ToolBase):
|
|
|
783
790
|
error_code="CONFIG_ERROR",
|
|
784
791
|
message="record_import_status_get accepts import_id or process_id_str, but not both at the same time",
|
|
785
792
|
extra={
|
|
793
|
+
"import_id": normalized_import_id,
|
|
794
|
+
"process_id_str": normalized_process_id,
|
|
786
795
|
"details": {
|
|
787
796
|
"fix_hint": "Use only one of `import_id` or `process_id_str`. You may pass `app_key` as an optional routing hint for direct method compatibility.",
|
|
788
797
|
}
|
|
@@ -793,6 +802,8 @@ class ImportTools(ToolBase):
|
|
|
793
802
|
error_code="CONFIG_ERROR",
|
|
794
803
|
message="record_import_status_get requires at least one selector: process_id_str, import_id, or app_key",
|
|
795
804
|
extra={
|
|
805
|
+
"import_id": normalized_import_id,
|
|
806
|
+
"process_id_str": normalized_process_id,
|
|
796
807
|
"details": {
|
|
797
808
|
"fix_hint": "Use `process_id_str` or `import_id` for a known import, or use only `app_key` to inspect the latest import in that app.",
|
|
798
809
|
}
|
|
@@ -806,6 +817,9 @@ class ImportTools(ToolBase):
|
|
|
806
817
|
if local_job is None and normalized_process_id:
|
|
807
818
|
matches = [item for item in self._job_store.list() if _normalize_optional_text(item.get("process_id_str")) == normalized_process_id]
|
|
808
819
|
local_job = matches[0] if len(matches) == 1 else None
|
|
820
|
+
effective_process_id = normalized_process_id
|
|
821
|
+
if effective_process_id is None and isinstance(local_job, dict):
|
|
822
|
+
effective_process_id = _normalize_optional_text(local_job.get("process_id_str"))
|
|
809
823
|
resolved_app_key = normalized_app_key
|
|
810
824
|
if not resolved_app_key and isinstance(local_job, dict):
|
|
811
825
|
resolved_app_key = str(local_job.get("app_key") or "").strip()
|
|
@@ -814,6 +828,8 @@ class ImportTools(ToolBase):
|
|
|
814
828
|
error_code="CONFIG_ERROR",
|
|
815
829
|
message="record_import_status_get could not determine app_key from the provided selector",
|
|
816
830
|
extra={
|
|
831
|
+
"import_id": normalized_import_id,
|
|
832
|
+
"process_id_str": effective_process_id,
|
|
817
833
|
"details": {
|
|
818
834
|
"fix_hint": "Use the original `app_key`, or call import status with the latest-import mode: only `app_key`.",
|
|
819
835
|
}
|
|
@@ -832,13 +848,18 @@ class ImportTools(ToolBase):
|
|
|
832
848
|
matched_record, matched_by = _match_import_record(
|
|
833
849
|
records,
|
|
834
850
|
local_job=local_job,
|
|
835
|
-
|
|
851
|
+
import_id=normalized_import_id,
|
|
852
|
+
process_id_str=effective_process_id,
|
|
836
853
|
)
|
|
837
854
|
if matched_record is None:
|
|
838
855
|
return self._failed_status_result(
|
|
839
856
|
error_code="IMPORT_STATUS_AMBIGUOUS",
|
|
840
857
|
message="could not uniquely resolve an import record from the provided identifiers",
|
|
841
|
-
extra={
|
|
858
|
+
extra={
|
|
859
|
+
"import_id": normalized_import_id,
|
|
860
|
+
"process_id_str": effective_process_id,
|
|
861
|
+
"matched_by": matched_by,
|
|
862
|
+
},
|
|
842
863
|
)
|
|
843
864
|
normalized_process = _normalize_optional_text(
|
|
844
865
|
matched_record.get("processIdStr") or matched_record.get("processId") or matched_record.get("process_id_str")
|
|
@@ -852,13 +873,26 @@ class ImportTools(ToolBase):
|
|
|
852
873
|
"process_id_str": normalized_process,
|
|
853
874
|
},
|
|
854
875
|
)
|
|
876
|
+
raw_process_status = matched_record.get("processStatus")
|
|
855
877
|
total_rows = _coerce_int(matched_record.get("totalNumber") or matched_record.get("total_rows"))
|
|
856
878
|
success_rows = _coerce_int(matched_record.get("successNum") or matched_record.get("success_rows"))
|
|
857
879
|
failed_rows = _coerce_int(matched_record.get("errorNum") or matched_record.get("failed_rows"))
|
|
858
880
|
progress = _coerce_int(matched_record.get("importPercentage") or matched_record.get("progress"))
|
|
881
|
+
normalized_status = _normalize_import_status(raw_process_status)
|
|
882
|
+
warnings: list[dict[str, str]] = []
|
|
883
|
+
if normalized_status in {"succeeded", "failed", "partially_failed"} and all(
|
|
884
|
+
value is None for value in (total_rows, success_rows, failed_rows)
|
|
885
|
+
):
|
|
886
|
+
warnings.append(
|
|
887
|
+
{
|
|
888
|
+
"code": "IMPORT_STATUS_COUNTERS_MISSING",
|
|
889
|
+
"message": "backend import history returned a terminal process status without row counters",
|
|
890
|
+
}
|
|
891
|
+
)
|
|
859
892
|
return {
|
|
860
893
|
"ok": True,
|
|
861
|
-
"status":
|
|
894
|
+
"status": normalized_status,
|
|
895
|
+
"process_status": _coerce_int(raw_process_status),
|
|
862
896
|
"app_key": resolved_app_key,
|
|
863
897
|
"import_id": normalized_import_id or (local_job.get("import_id") if isinstance(local_job, dict) else None),
|
|
864
898
|
"process_id_str": normalized_process,
|
|
@@ -871,7 +905,7 @@ class ImportTools(ToolBase):
|
|
|
871
905
|
"error_file_urls": _normalize_error_file_urls(matched_record.get("errorFileUrls")),
|
|
872
906
|
"operate_time": matched_record.get("operateTime"),
|
|
873
907
|
"operate_user": matched_record.get("operateUser"),
|
|
874
|
-
"warnings":
|
|
908
|
+
"warnings": warnings,
|
|
875
909
|
"verification": {
|
|
876
910
|
"status_lookup_completed": True,
|
|
877
911
|
"matched_by": matched_by,
|
|
@@ -2118,6 +2152,7 @@ def _match_import_record(
|
|
|
2118
2152
|
records: list[JSONObject],
|
|
2119
2153
|
*,
|
|
2120
2154
|
local_job: dict[str, Any] | None,
|
|
2155
|
+
import_id: str | None,
|
|
2121
2156
|
process_id_str: str | None,
|
|
2122
2157
|
) -> tuple[JSONObject | None, str | None]:
|
|
2123
2158
|
if process_id_str:
|
|
@@ -2130,6 +2165,16 @@ def _match_import_record(
|
|
|
2130
2165
|
return exact[0], "process_id_str"
|
|
2131
2166
|
if len(exact) > 1:
|
|
2132
2167
|
return None, "process_id_str"
|
|
2168
|
+
if import_id:
|
|
2169
|
+
exact = [
|
|
2170
|
+
item
|
|
2171
|
+
for item in records
|
|
2172
|
+
if import_id in _extract_import_record_ids(item)
|
|
2173
|
+
]
|
|
2174
|
+
if len(exact) == 1:
|
|
2175
|
+
return exact[0], "import_id"
|
|
2176
|
+
if len(exact) > 1:
|
|
2177
|
+
return None, "import_id"
|
|
2133
2178
|
if isinstance(local_job, dict):
|
|
2134
2179
|
source_file_name = _normalize_optional_text(local_job.get("source_file_name"))
|
|
2135
2180
|
started_at = _parse_utc(local_job.get("started_at"))
|
|
@@ -2160,6 +2205,15 @@ def _match_import_record(
|
|
|
2160
2205
|
return None, None
|
|
2161
2206
|
|
|
2162
2207
|
|
|
2208
|
+
def _extract_import_record_ids(record: JSONObject) -> set[str]:
|
|
2209
|
+
identifiers: set[str] = set()
|
|
2210
|
+
for key in ("importId", "import_id", "dataImportId", "data_import_id"):
|
|
2211
|
+
normalized = _normalize_optional_text(record.get(key))
|
|
2212
|
+
if normalized:
|
|
2213
|
+
identifiers.add(normalized)
|
|
2214
|
+
return identifiers
|
|
2215
|
+
|
|
2216
|
+
|
|
2163
2217
|
def _parse_utc(value: Any) -> datetime | None:
|
|
2164
2218
|
text = _normalize_optional_text(value)
|
|
2165
2219
|
if text is None:
|
|
@@ -2183,6 +2237,26 @@ def _coerce_int(value: Any) -> int | None:
|
|
|
2183
2237
|
return None
|
|
2184
2238
|
|
|
2185
2239
|
|
|
2240
|
+
def _normalize_import_status(value: Any) -> str:
|
|
2241
|
+
status_code = _coerce_int(value)
|
|
2242
|
+
if status_code is not None:
|
|
2243
|
+
return IMPORT_STATUS_BY_PROCESS_STATUS.get(status_code, "unknown")
|
|
2244
|
+
text = str(value or "").strip().lower()
|
|
2245
|
+
if text in {"queued", "running", "succeeded", "failed", "partially_failed", "unknown"}:
|
|
2246
|
+
return text
|
|
2247
|
+
if text in {"line_up", "lineup"}:
|
|
2248
|
+
return "queued"
|
|
2249
|
+
if text in {"execute", "executing", "processing"}:
|
|
2250
|
+
return "running"
|
|
2251
|
+
if text in {"success", "completed"}:
|
|
2252
|
+
return "succeeded"
|
|
2253
|
+
if text in {"partly_fail", "partial_fail", "partially_fail", "partial_failed"}:
|
|
2254
|
+
return "partially_failed"
|
|
2255
|
+
if text in {"fail", "error"}:
|
|
2256
|
+
return "failed"
|
|
2257
|
+
return "unknown"
|
|
2258
|
+
|
|
2259
|
+
|
|
2186
2260
|
def _normalize_error_file_urls(value: Any) -> list[str]:
|
|
2187
2261
|
if isinstance(value, list):
|
|
2188
2262
|
return [str(item).strip() for item in value if str(item).strip()]
|