@josephyan/qingflow-cli 1.0.11 → 1.1.2
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 +3 -3
- package/npm/bin/qingflow.mjs +40 -2
- package/npm/lib/runtime.mjs +386 -15
- package/npm/scripts/postinstall.mjs +7 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +440 -0
- package/skills/qingflow-cli/manifest.yaml +10 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
- package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
- package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
- package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
- package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
- package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
- package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +532 -48
- package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +354 -56
- package/src/qingflow_mcp/cli/commands/record.py +89 -2
- package/src/qingflow_mcp/cli/formatters.py +32 -1
- package/src/qingflow_mcp/cli/main.py +245 -3
- package/src/qingflow_mcp/public_surface.py +11 -8
- package/src/qingflow_mcp/response_trim.py +143 -14
- package/src/qingflow_mcp/server.py +15 -12
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +17 -18
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +2095 -176
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/version.py +110 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -57,6 +57,13 @@ def trim_public_response(tool_name: str | None, payload: dict[str, Any]) -> dict
|
|
|
57
57
|
if not isinstance(payload, dict):
|
|
58
58
|
return payload
|
|
59
59
|
if _looks_like_failure_payload(payload):
|
|
60
|
+
status = str(payload.get("status") or "").lower()
|
|
61
|
+
if tool_name in {"user:record_insert", "user:record_update", "user:record_delete"} and status in {
|
|
62
|
+
"blocked",
|
|
63
|
+
"needs_confirmation",
|
|
64
|
+
"partial_success",
|
|
65
|
+
}:
|
|
66
|
+
return trim_success_response(tool_name, payload)
|
|
60
67
|
return _trim_returned_failure(payload)
|
|
61
68
|
return trim_success_response(tool_name, payload)
|
|
62
69
|
|
|
@@ -66,8 +73,10 @@ def trim_success_response(tool_name: str | None, payload: dict[str, Any]) -> dic
|
|
|
66
73
|
return payload
|
|
67
74
|
trimmed = deepcopy(payload)
|
|
68
75
|
drop_keys = COMMON_SUCCESS_DROP_TOP
|
|
69
|
-
if tool_name
|
|
76
|
+
if tool_name in {"user:record_get", "user:record_logs_get"}:
|
|
70
77
|
drop_keys = COMMON_SUCCESS_DROP_TOP - {"output_profile"}
|
|
78
|
+
if tool_name in {"user:record_insert", "user:record_update", "user:record_delete"} and payload.get("ok") is False:
|
|
79
|
+
drop_keys = drop_keys - {"ok"}
|
|
71
80
|
_drop_top_keys(trimmed, drop_keys)
|
|
72
81
|
transformer = SUCCESS_POLICY_BY_TOOL.get(tool_name or "")
|
|
73
82
|
if transformer is not None:
|
|
@@ -84,7 +93,12 @@ def trim_error_response(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
84
93
|
_drop_deep_keys(trimmed.get("details"), {"request_route", "base_url", "normalized_args", "suggested_next_call", "transport", "response", "body", "raw"})
|
|
85
94
|
details = trimmed.get("details")
|
|
86
95
|
if isinstance(details, dict):
|
|
96
|
+
preserved = {}
|
|
97
|
+
for key in ("blocking_issues", "compiled_match_rules"):
|
|
98
|
+
if key in details:
|
|
99
|
+
preserved[key] = details.get(key)
|
|
87
100
|
compact_details = _compact_scalar_dict(details)
|
|
101
|
+
compact_details.update(preserved)
|
|
88
102
|
if compact_details:
|
|
89
103
|
trimmed["details"] = compact_details
|
|
90
104
|
else:
|
|
@@ -140,7 +154,7 @@ def _looks_like_failure_payload(payload: dict[str, Any]) -> bool:
|
|
|
140
154
|
if payload.get("ok") is False:
|
|
141
155
|
return True
|
|
142
156
|
status = str(payload.get("status") or "").lower()
|
|
143
|
-
return status in {"failed", "blocked"
|
|
157
|
+
return status in {"failed", "blocked"}
|
|
144
158
|
|
|
145
159
|
|
|
146
160
|
def _trim_returned_failure(payload: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -149,7 +163,12 @@ def _trim_returned_failure(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
149
163
|
_drop_deep_keys(trimmed.get("details"), {"request_route", "base_url", "normalized_args", "suggested_next_call", "transport", "response", "body", "raw"})
|
|
150
164
|
details = trimmed.get("details")
|
|
151
165
|
if isinstance(details, dict):
|
|
166
|
+
preserved = {}
|
|
167
|
+
for key in ("blocking_issues", "compiled_match_rules"):
|
|
168
|
+
if key in details:
|
|
169
|
+
preserved[key] = details.get(key)
|
|
152
170
|
compact_details = _compact_scalar_dict(details)
|
|
171
|
+
compact_details.update(preserved)
|
|
153
172
|
if compact_details:
|
|
154
173
|
trimmed["details"] = compact_details
|
|
155
174
|
else:
|
|
@@ -275,7 +294,7 @@ def _trim_workspace_get(payload: JSONObject) -> None:
|
|
|
275
294
|
)
|
|
276
295
|
|
|
277
296
|
|
|
278
|
-
def
|
|
297
|
+
def _trim_app_list_like(payload: JSONObject) -> None:
|
|
279
298
|
payload.pop("apps", None)
|
|
280
299
|
_trim_item_list(payload, "items", allowed=("app_key", "app_name", "package_name"))
|
|
281
300
|
|
|
@@ -368,10 +387,17 @@ def _trim_record_write(payload: JSONObject) -> None:
|
|
|
368
387
|
data = payload.get("data")
|
|
369
388
|
if not isinstance(data, dict):
|
|
370
389
|
return
|
|
390
|
+
if payload.get("mode") == "batch" or data.get("mode") == "batch":
|
|
391
|
+
_trim_record_write_batch(payload, data)
|
|
392
|
+
return
|
|
371
393
|
data.pop("debug", None)
|
|
372
394
|
data.pop("normalized_payload", None)
|
|
373
395
|
data.pop("human_review", None)
|
|
374
396
|
data.pop("action", None)
|
|
397
|
+
for key in ("update_route", "tried_routes"):
|
|
398
|
+
value = payload.get(key)
|
|
399
|
+
if value not in (None, [], {}, ""):
|
|
400
|
+
data[key] = value
|
|
375
401
|
resource = _compact_record_resource(data.get("resource"))
|
|
376
402
|
if resource:
|
|
377
403
|
data["resource"] = resource
|
|
@@ -397,6 +423,44 @@ def _trim_record_write(payload: JSONObject) -> None:
|
|
|
397
423
|
data.pop(key, None)
|
|
398
424
|
|
|
399
425
|
|
|
426
|
+
def _trim_record_write_batch(payload: JSONObject, data: JSONObject) -> None:
|
|
427
|
+
data.pop("items", None)
|
|
428
|
+
data.pop("debug", None)
|
|
429
|
+
for key in ("summary", "app_key", "mode"):
|
|
430
|
+
if data.get(key) in (None, [], {}, ""):
|
|
431
|
+
data.pop(key, None)
|
|
432
|
+
items = payload.get("items")
|
|
433
|
+
if isinstance(items, list):
|
|
434
|
+
payload["items"] = [
|
|
435
|
+
_pick(
|
|
436
|
+
item,
|
|
437
|
+
(
|
|
438
|
+
"index",
|
|
439
|
+
"row_number",
|
|
440
|
+
"status",
|
|
441
|
+
"record_id",
|
|
442
|
+
"apply_id",
|
|
443
|
+
"write_executed",
|
|
444
|
+
"verification_status",
|
|
445
|
+
"safe_to_retry",
|
|
446
|
+
"update_route",
|
|
447
|
+
"tried_routes",
|
|
448
|
+
"failed_fields",
|
|
449
|
+
"confirmation_requests",
|
|
450
|
+
"blockers",
|
|
451
|
+
"error",
|
|
452
|
+
"warnings",
|
|
453
|
+
"resource",
|
|
454
|
+
"verification",
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
for item in items
|
|
458
|
+
if isinstance(item, dict)
|
|
459
|
+
]
|
|
460
|
+
if payload.get("items") in (None, [], {}, ""):
|
|
461
|
+
payload.pop("items", None)
|
|
462
|
+
|
|
463
|
+
|
|
400
464
|
def _trim_record_get(payload: JSONObject) -> None:
|
|
401
465
|
if payload.get("fields") is not None or payload.get("semantic_context") is not None:
|
|
402
466
|
_trim_detail_context_record_get(payload)
|
|
@@ -538,7 +602,7 @@ def _trim_detail_context_record_get(payload: JSONObject) -> None:
|
|
|
538
602
|
associated_resources = payload.get("associated_resources")
|
|
539
603
|
if isinstance(associated_resources, list):
|
|
540
604
|
payload["associated_resources"] = [
|
|
541
|
-
_pick(item, ("type", "name", "app_key", "app_name", "view_key", "chart_key", "view_type", "data_access"))
|
|
605
|
+
_pick(item, ("type", "resource_type", "name", "app_key", "app_name", "view_key", "chart_key", "view_type", "report_source", "data_access"))
|
|
542
606
|
for item in associated_resources
|
|
543
607
|
if isinstance(item, dict)
|
|
544
608
|
]
|
|
@@ -610,6 +674,49 @@ def _trim_record_access(payload: JSONObject) -> None:
|
|
|
610
674
|
payload.update(compact)
|
|
611
675
|
|
|
612
676
|
|
|
677
|
+
def _trim_record_logs(payload: JSONObject) -> None:
|
|
678
|
+
compact: dict[str, Any] = {}
|
|
679
|
+
for key in (
|
|
680
|
+
"ok",
|
|
681
|
+
"status",
|
|
682
|
+
"output_profile",
|
|
683
|
+
"app",
|
|
684
|
+
"view",
|
|
685
|
+
"record",
|
|
686
|
+
"local_dir",
|
|
687
|
+
"summary_path",
|
|
688
|
+
"warnings",
|
|
689
|
+
"unavailable_context",
|
|
690
|
+
"context_integrity",
|
|
691
|
+
):
|
|
692
|
+
value = payload.get(key)
|
|
693
|
+
if value is not None:
|
|
694
|
+
compact[key] = value
|
|
695
|
+
for key in ("data_logs", "workflow_logs"):
|
|
696
|
+
node = payload.get(key)
|
|
697
|
+
if isinstance(node, dict):
|
|
698
|
+
compact[key] = _pick(
|
|
699
|
+
node,
|
|
700
|
+
(
|
|
701
|
+
"status",
|
|
702
|
+
"visible",
|
|
703
|
+
"source",
|
|
704
|
+
"reason",
|
|
705
|
+
"complete",
|
|
706
|
+
"items_count",
|
|
707
|
+
"pages_fetched",
|
|
708
|
+
"page_size",
|
|
709
|
+
"reported_total",
|
|
710
|
+
"local_path",
|
|
711
|
+
"preview_items",
|
|
712
|
+
"warnings",
|
|
713
|
+
"stopped_reason",
|
|
714
|
+
),
|
|
715
|
+
)
|
|
716
|
+
payload.clear()
|
|
717
|
+
payload.update(compact)
|
|
718
|
+
|
|
719
|
+
|
|
613
720
|
def _trim_record_analyze(payload: JSONObject) -> None:
|
|
614
721
|
summary: dict[str, Any] = {}
|
|
615
722
|
completeness = payload.get("completeness")
|
|
@@ -656,13 +763,18 @@ def _trim_record_delete(payload: JSONObject) -> None:
|
|
|
656
763
|
if not isinstance(data, dict):
|
|
657
764
|
return
|
|
658
765
|
resource = data.get("resource")
|
|
659
|
-
deleted_ids
|
|
660
|
-
if isinstance(
|
|
766
|
+
deleted_ids = payload.get("deleted_ids") if isinstance(payload.get("deleted_ids"), list) else data.get("deleted_ids")
|
|
767
|
+
failed_ids = payload.get("failed_ids") if isinstance(payload.get("failed_ids"), list) else data.get("failed_ids")
|
|
768
|
+
if not isinstance(deleted_ids, list):
|
|
769
|
+
deleted_ids = []
|
|
770
|
+
if not isinstance(failed_ids, list):
|
|
771
|
+
failed_ids = []
|
|
772
|
+
if not deleted_ids and isinstance(resource, dict):
|
|
661
773
|
raw_ids = resource.get("record_ids") or resource.get("apply_ids") or resource.get("applyIds")
|
|
662
774
|
if isinstance(raw_ids, list):
|
|
663
775
|
deleted_ids = [str(item) for item in raw_ids if item not in (None, "")]
|
|
664
|
-
data["deleted_ids"] = deleted_ids
|
|
665
|
-
data
|
|
776
|
+
data["deleted_ids"] = [str(item) for item in deleted_ids if item not in (None, "")]
|
|
777
|
+
data["failed_ids"] = [str(item) for item in failed_ids if item not in (None, "")]
|
|
666
778
|
for key in (
|
|
667
779
|
"resource",
|
|
668
780
|
"action",
|
|
@@ -730,6 +842,19 @@ def _compact_schema_field(item: Any, *, template_map: dict[str, Any] | None) ->
|
|
|
730
842
|
compact["required"] = bool(item.get("required"))
|
|
731
843
|
if template_map is not None and isinstance(title, str) and title in template_map:
|
|
732
844
|
compact["template"] = template_map.get(title)
|
|
845
|
+
for key in (
|
|
846
|
+
"format_hint",
|
|
847
|
+
"example_value",
|
|
848
|
+
"linkage",
|
|
849
|
+
"may_become_required",
|
|
850
|
+
"activation_sources",
|
|
851
|
+
"requirement_reason",
|
|
852
|
+
"accepts_natural_input",
|
|
853
|
+
"requires_upload",
|
|
854
|
+
):
|
|
855
|
+
value = item.get(key)
|
|
856
|
+
if value not in (None, [], {}, ""):
|
|
857
|
+
compact[key] = value
|
|
733
858
|
candidate_hint = item.get("candidate_hint")
|
|
734
859
|
if isinstance(candidate_hint, dict):
|
|
735
860
|
compact["candidate_hint"] = candidate_hint
|
|
@@ -883,7 +1008,11 @@ def _trim_builder_envelope(payload: JSONObject) -> None:
|
|
|
883
1008
|
details = payload.get("details")
|
|
884
1009
|
if isinstance(details, dict):
|
|
885
1010
|
_drop_deep_keys(details, {"request_route", "base_url", "normalized_args", "suggested_next_call", "transport", "response", "body", "raw"})
|
|
1011
|
+
preserved = {}
|
|
1012
|
+
if isinstance(details.get("compiled_match_rules"), dict):
|
|
1013
|
+
preserved["compiled_match_rules"] = details.get("compiled_match_rules")
|
|
886
1014
|
compact = _compact_scalar_dict(details)
|
|
1015
|
+
compact.update(preserved)
|
|
887
1016
|
if compact:
|
|
888
1017
|
payload["details"] = compact
|
|
889
1018
|
else:
|
|
@@ -904,7 +1033,7 @@ _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("auth_use_credential", "auth_wh
|
|
|
904
1033
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("auth_logout",), _trim_auth_logout)
|
|
905
1034
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_list",), _trim_workspace_list)
|
|
906
1035
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_get", "workspace_select"), _trim_workspace_get)
|
|
907
|
-
_register_policy((USER_DOMAIN,), ("app_list",
|
|
1036
|
+
_register_policy((USER_DOMAIN,), ("app_list",), _trim_app_list_like)
|
|
908
1037
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("app_get",), _trim_app_get)
|
|
909
1038
|
_register_policy((BUILDER_DOMAIN,), ("app_repair_code_blocks",), _trim_builder_list_like)
|
|
910
1039
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("portal_list", "portal_get", "view_get", "chart_get"), _trim_builder_list_like)
|
|
@@ -947,6 +1076,7 @@ _register_policy((USER_DOMAIN,), ("record_insert", "record_update"), _trim_recor
|
|
|
947
1076
|
_register_policy((USER_DOMAIN,), ("record_get",), _trim_record_get)
|
|
948
1077
|
_register_policy((USER_DOMAIN,), ("record_list",), _trim_record_list)
|
|
949
1078
|
_register_policy((USER_DOMAIN,), ("record_access",), _trim_record_access)
|
|
1079
|
+
_register_policy((USER_DOMAIN,), ("record_logs_get",), _trim_record_logs)
|
|
950
1080
|
_register_policy((USER_DOMAIN,), ("record_analyze",), _trim_record_analyze)
|
|
951
1081
|
_register_policy((USER_DOMAIN,), ("record_code_block_run",), _trim_code_block_run)
|
|
952
1082
|
_register_policy((USER_DOMAIN,), ("task_list",), _trim_task_list)
|
|
@@ -987,6 +1117,8 @@ _register_policy(
|
|
|
987
1117
|
(BUILDER_DOMAIN,),
|
|
988
1118
|
(
|
|
989
1119
|
"builder_tool_contract",
|
|
1120
|
+
"workspace_icon_catalog_get",
|
|
1121
|
+
"package_list",
|
|
990
1122
|
"package_get",
|
|
991
1123
|
"package_apply",
|
|
992
1124
|
"solution_install",
|
|
@@ -996,11 +1128,8 @@ _register_policy(
|
|
|
996
1128
|
"app_release_edit_lock_if_mine",
|
|
997
1129
|
"app_resolve",
|
|
998
1130
|
"button_style_catalog_get",
|
|
999
|
-
"
|
|
1000
|
-
"
|
|
1001
|
-
"app_custom_button_create",
|
|
1002
|
-
"app_custom_button_update",
|
|
1003
|
-
"app_custom_button_delete",
|
|
1131
|
+
"app_custom_buttons_apply",
|
|
1132
|
+
"app_associated_resources_apply",
|
|
1004
1133
|
"app_get_fields",
|
|
1005
1134
|
"app_repair_code_blocks",
|
|
1006
1135
|
"app_get_layout",
|
|
@@ -48,7 +48,7 @@ All resource tools operate with the logged-in user's Qingflow permissions.
|
|
|
48
48
|
|
|
49
49
|
## App Discovery
|
|
50
50
|
|
|
51
|
-
If `app_key` is unknown, use `app_list`
|
|
51
|
+
If `app_key` is unknown, use `app_list` first. Pass `query` to filter visible apps by keyword.
|
|
52
52
|
If the app is known but the data range is not, use `app_get` first and choose from `accessible_views`.
|
|
53
53
|
If an accessible view has `analysis_supported=false`, do not use it for `record_access` or `record_list`. `boardView` and `ganttView` are special UI views, not data-access targets.
|
|
54
54
|
`view_get(view_id=...)` also returns `export_capability`; it only means there is a supported export route, not that export permission has been verified.
|
|
@@ -56,9 +56,9 @@ If an accessible view has `analysis_supported=false`, do not use it for `record_
|
|
|
56
56
|
## Schema-First Rule
|
|
57
57
|
|
|
58
58
|
Call `record_insert_schema_get` before `record_insert`.
|
|
59
|
-
|
|
59
|
+
For simple field changes after the target record is clear, call `record_update` directly. Use `record_update_schema_get` for diagnostics, ambiguous fields, or complex writable-scope inspection.
|
|
60
60
|
Call `record_code_block_schema_get` before `record_code_block_run`.
|
|
61
|
-
Call `app_get` first when the data range is unclear, then use `record_browse_schema_get(view_id=...)` before `record_access`, `record_list`, or `
|
|
61
|
+
Call `app_get` first when the data range is unclear, then use `record_browse_schema_get(view_id=...)` before `record_access`, `record_list`, `record_get`, or `record_logs_get`.
|
|
62
62
|
Call `record_import_schema_get` when the import field mapping is unclear before template download or verify.
|
|
63
63
|
|
|
64
64
|
- All `field_id` values must come from the schema response.
|
|
@@ -67,7 +67,7 @@ Call `record_import_schema_get` when the import field mapping is unclear before
|
|
|
67
67
|
## Schema Scope
|
|
68
68
|
|
|
69
69
|
`record_insert_schema_get` returns the current user's insert-ready applicant schema; read `required_fields`, `optional_fields`, `runtime_linked_required_fields`, and `payload_template`.
|
|
70
|
-
`record_update_schema_get` returns the current record's overall update-ready writable field set across matched accessible views; read `writable_fields` and `
|
|
70
|
+
`record_update_schema_get` returns the current record's overall update-ready writable field set and route diagnostics across matched accessible views; read `writable_fields`, `payload_template`, `available_update_routes`, and `recommended_update_route`.
|
|
71
71
|
`record_browse_schema_get(view_id=...)` returns the same readable fields shown in the selected Qingflow table view header.
|
|
72
72
|
`record_access.fields` / CSV columns and `record_list.columns / where / order_by / query_fields` use that exact same view schema.
|
|
73
73
|
`record_code_block_schema_get` returns code-block-ready schema for exact code block field selection.
|
|
@@ -103,9 +103,9 @@ Analysis answers must include concrete numbers. When applicable, include percent
|
|
|
103
103
|
|
|
104
104
|
## Record CRUD Path
|
|
105
105
|
|
|
106
|
-
`app_get -> record_browse_schema_get(view_id=...) -> record_list / record_get`
|
|
107
|
-
`record_insert_schema_get -> record_insert`
|
|
108
|
-
`record_update_schema_get -> record_update`
|
|
106
|
+
`app_get -> record_browse_schema_get(view_id=...) -> record_list / record_get / record_logs_get`
|
|
107
|
+
`record_insert_schema_get -> record_insert(items)`
|
|
108
|
+
`record_update` for simple updates; `record_update_schema_get -> record_update` when the writable field scope is unclear.
|
|
109
109
|
`record_list / record_get -> record_delete`
|
|
110
110
|
`record_code_block_schema_get -> record_code_block_run`
|
|
111
111
|
|
|
@@ -115,22 +115,25 @@ Analysis answers must include concrete numbers. When applicable, include percent
|
|
|
115
115
|
- Use `order_by` items as `{{field_id, direction}}`
|
|
116
116
|
- Legacy forms such as bare integer `field_id`, `fieldId`, `operator`, `values`, or `order` may still parse, but they are compatibility-only and not the canonical DSL
|
|
117
117
|
|
|
118
|
-
- `record_insert`
|
|
119
|
-
- `record_update` uses a field-title keyed `fields` map
|
|
118
|
+
- `record_insert` defaults to an applicant-node `items` array; each item contains a field-title keyed `fields` map. A single insert is one item.
|
|
119
|
+
- `record_update` uses a field-title keyed `fields` map. It first tries the data-manager direct update route, then falls back to the frontend custom-view detail edit route when the selected view can cover the payload; if a unique current-user todo task for the same record exposes editable fields, it can finally use the workflow save-only route. Read `update_route` and `tried_routes` after execution.
|
|
120
120
|
- For insert, `runtime_linked_required_fields` means required-but-not-directly-writable fields that are usually supplied by runtime linkage or upstream context.
|
|
121
121
|
- For insert, fields marked `may_become_required=true` stay in `optional_fields`; they are still directly writable, but linked visibility or option-driven rules can make them required at runtime.
|
|
122
122
|
- Read field-level `linkage` whenever present on `record_insert_schema_get` or `record_update_schema_get`; it is the static hint for linked visibility, reference-driven auto fill, and formula/default auto-fill behavior.
|
|
123
123
|
- `linkage.sources` lists upstream field titles that influence the current field; `linkage.affects_fields` lists downstream fields that may change when the current field changes.
|
|
124
124
|
- `linkage.kind=logic_visibility` means linked visibility or option-driven rules are involved; `linkage.kind=reference_fill` means reference/default matching logic is involved; `linkage.kind=formula_fill` means formula/default auto-fill logic is involved.
|
|
125
|
-
- `record_update_schema_get` exposes the overall writable field set for the record, but not every field combination is guaranteed; `record_update` still needs one single matched
|
|
125
|
+
- `record_update_schema_get` exposes the overall writable field set and route candidates for the record, but not every field combination is guaranteed; `record_update` still needs data-manager permission, one single matched custom view that can cover the payload, or one unique editable current-user todo task.
|
|
126
126
|
- `record_delete` deletes by `record_id` or `record_ids`.
|
|
127
127
|
- `record_get` is the single-record frontend detail context tool. It returns detail-page visible fields, one-level relation targets, first-page data/workflow logs, associated views/reports, local readable image assets, local downloadable file assets, unavailable context, and `semantic_context`.
|
|
128
|
+
- Use `record_logs_get` only when the user needs the full visible data/workflow log history for a specific record. It writes JSONL files locally and returns file paths plus completeness metadata; do not expect full log arrays in the response.
|
|
128
129
|
- Read record images from `record_get.media_assets.items[].local_path` when `readable_by_agent=true`; read attachments/documents/tables from `record_get.file_assets.items[].local_path` and `extraction.text_path` when present. `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, and remote file URLs should not be treated as directly readable.
|
|
129
130
|
- `record_get.columns` are focus hints only; they do not project the detail fields. Read facts from top-level `fields[]`.
|
|
130
131
|
- When readback shape matters after insert or update, prefer `record_get` for human/detail-page context, or `record_list(..., output_profile="normalized")` for batch-shaped normalized rows.
|
|
131
132
|
|
|
132
133
|
- Read relation targets from `record_insert_schema_get` / `record_update_schema_get` relation metadata before preparing relation writes.
|
|
133
|
-
- Member and
|
|
134
|
+
- Member, department, and relation fields may be written with natural strings directly on `record_insert` / `record_update`; only fall back to candidate tools when the user wants explicit candidate browsing or the write returns ambiguity that needs confirmation.
|
|
135
|
+
- CLI-only agents can use `qingflow record member-candidates --app-key APP_KEY --field-id FIELD_ID --keyword NAME --json` or `qingflow record department-candidates --app-key APP_KEY --field-id FIELD_ID --keyword DEPT --json` for that fallback; pass `--record-id`, `--workflow-node-id`, and `--fields-file` only when the candidate scope must match an existing runtime context.
|
|
136
|
+
- For batch insert `partial_success`, read `created_record_ids`, failed `items[].row_number`, and `failed_fields`; repair only failed rows and never retry the whole batch after any row has `write_executed=true`.
|
|
134
137
|
- If explicit candidate browsing is needed for default-all member or department fields, prefer those field candidate tools instead of starting with `directory_*`.
|
|
135
138
|
|
|
136
139
|
## Code Block Path
|
|
@@ -183,7 +186,7 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
183
186
|
- `task_action_execute(task_id=..., action=...)` is also supported; MCP resolves the current todo locator internally before calling the real action route.
|
|
184
187
|
- `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
|
|
185
188
|
- Use `task_associated_report_detail_get` for associated view or report details.
|
|
186
|
-
- Use `task_workflow_log_get` for
|
|
189
|
+
- Use `task_workflow_log_get` for the current task context workflow log page. For full record-level data/workflow logs, use `record_logs_get(app_key, record_id, view_id?)`.
|
|
187
190
|
- Task actions operate on `app_key + record_id + workflow_node_id`, not `task_id`.
|
|
188
191
|
|
|
189
192
|
## Time Handling
|
|
@@ -35,13 +35,23 @@ def build_builder_server() -> FastMCP:
|
|
|
35
35
|
"Follow the resource path resolve -> summary read -> apply -> publish_verify. "
|
|
36
36
|
"Use builder_tool_contract when you need a machine-readable contract, aliases, allowed enums, or a minimal valid example for a public builder tool. "
|
|
37
37
|
"Use solution_install when the user explicitly wants to install a packaged solution/template by solution_key, optionally copying bundled demo data. "
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"Use package_list to find visible app packages by keyword and package_get to read package detail before editing; if creating or updating an app package may be appropriate, use package_apply with explicit user intent; otherwise use app_resolve to locate app resources, "
|
|
39
|
+
"Use workspace_icon_catalog_get before creating app packages, apps, or portals when supported icon/color candidates are needed; new workspace resources require explicit non-template icon + color, and the CLI validates choices without inferring business defaults. "
|
|
40
|
+
"app_get as the default app map read, then app_get_fields/app_repair_code_blocks/app_get_layout/app_get_views/app_get_flow/app_flow_get/app_flow_get_schema/app_get_charts/portal_list/portal_get/view_get/chart_get for focused configuration reads, "
|
|
40
41
|
"member_search/role_search/role_create when workflow assignees must come from the directory or role catalog, preferring roles over explicit members unless the user explicitly names members, "
|
|
41
|
-
"then app_schema_apply/app_layout_apply/app_flow_apply/app_views_apply/app_charts_apply/portal_apply to execute normalized patches; these apply tools perform planning, normalization, and dependency checks internally where applicable. Schema/layout/views noop requests skip publish, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, publish=false only guarantees draft/base-info updates, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
42
|
+
"then app_schema_apply/app_layout_apply/app_flow_apply/app_views_apply/app_custom_buttons_apply/app_associated_resources_apply/app_charts_apply/portal_apply to execute normalized patches; these apply tools perform planning, normalization, and dependency checks internally where applicable. Schema/layout/views noop requests skip publish, app_custom_buttons_apply and app_associated_resources_apply publish after at least one write succeeds and expose no draft-only parameter, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, portal pc layout is a 24-column grid and mobile is a 6-column grid so omit position or use layout_preset when unsure, publish=false only guarantees draft/base-info updates for tools that still expose that parameter, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
43
|
+
"Builder apply/write outputs include schema_version, operation, summary, and resources[]; use resources[].id/key/name/ids/parent as the stable UI and agent display entry, and keep legacy fields such as field_diff/views_diff/chart_results only for compatibility or troubleshooting. "
|
|
44
|
+
"For existing object parameter replacement, prefer patch_views, patch_buttons, patch_resources, and patch_charts with set/unset; the tool reads current config and full-saves internally, while upsert_* is for creation or full target configuration and should not be used as an incomplete partial update. "
|
|
45
|
+
"For builder delete/remove apply results, separate delete execution from readback verification: after DELETE is sent, resources expose delete_executed, readback_status, and safe_to_retry_delete=false. If readback_status is unavailable or still_exists, do not blindly repeat the delete; confirm later with app_get/view_get/chart_get or the relevant apply readback. Views/buttons use single-item readback; associated resources use one app-level resource-pool readback because there is no confirmed single-item GET. "
|
|
46
|
+
"For app_schema_apply, configure data title and data cover directly in field JSON with as_data_title=true and as_data_cover=true; data title is required and exactly one field may be marked, while data cover is optional and must be a top-level attachment field. For multi-app creation, pass apps[]/--apps-file on app_schema_apply; each item may have client_key, and relation fields may use target_app_ref to point at another same-call client_key. "
|
|
47
|
+
"For app_views_apply, keep fixed saved filters in filters and configure the frontend query panel separately with query_conditions; query_conditions.rows is a matrix of field names compiled to backend queryCondition queIds. New views default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless associated_resources is explicitly patched. "
|
|
48
|
+
"For custom button body create/update/delete and view placement, use app_custom_buttons_apply. For addData buttons, prefer trigger_add_data_config.target_app_key + field_mappings/default_values; do not ask agents to write raw que_relation unless maintaining a legacy config. field_mappings.source_field accepts source schema fields and supported system fields: 数据ID/row_record_id/apply_id/_id means current record id (-17), 编号/record_number means visible record number (0). To fill a target relation with the current record, map {'source_field': '数据ID', 'target_field': '目标引用字段'}; default_values is only for static constants. View button bindings merge by default and merge-mode view_configs must include buttons; use view_configs[].mode=replace or explicit buttons=[] only when clearing/replacing existing bindings is intended. Builder view_key arguments are raw keys from app_get.views[].view_key and must not be prefixed with custom:. "
|
|
49
|
+
"For BI reports, keep report-body development separate from Qingflow in-app display: use app_charts_apply to create, update, remove, or reorder app-source QingBI chart bodies/configs with dataSourceType=qingflow; chart dimension/metric/filter/query fields must come from app_get_fields.chart_fields, not record schema or form-only fields; dataset BI reports are not created or edited by app_charts_apply yet and should be created in QingBI first, then attached with app_associated_resources_apply using report_source=dataset. "
|
|
50
|
+
"For associated views/reports, use app_associated_resources_apply. Use match_mappings for filtering associated resources: dynamic current-record conditions use source_field, static conditions use value. match_mappings also supports 数据ID(-17) and 编号(0). Do not ask agents to write raw match_rules unless preserving a legacy backend config. "
|
|
51
|
+
"For associated reports/views, use app_associated_resources_apply for both the app-level associated_resources pool and per-view display config; associated_item_id is the app-level form_asos_chart.id, and view_configs/remove/reorder may also pass an existing resource's chart_id/chart_key/view_key because the tool resolves those to the internal id. Before creating an associated resource, read app_get.associated_resources and reuse an existing matching target_app_key + view_key/chart_key through patch_resources; client_key only works inside one apply call and is not persisted. Do not ask agents to pass backend raw sourceType: views infer the internal Qingflow view source, reports default to BI app reports, and dataset reports use report_source=dataset. "
|
|
42
52
|
"For code_block fields with output bindings, always use qf_output assignment rather than const/let qf_output, and use app_repair_code_blocks when an existing form hangs because output-bound fields stay loading. "
|
|
43
53
|
"Use package_apply to manage package metadata, visibility, grouping, and ordering, and app_publish_verify for explicit final publish verification. "
|
|
44
|
-
"For workflow edits,
|
|
54
|
+
"For workflow edits, use app_flow_get_schema and app_flow_get (GET-first), author a complete WorkflowSpecDTO in spec, then app_flow_apply; CLI equivalent is qingflow builder flow schema|get|apply --spec-file. Do not use general-server legacy workflow config reads (auditNodes list/detail, global settings, qsource config reads); they are removed. "
|
|
45
55
|
"If builder writes are blocked by the current user's own edit lock, use app_release_edit_lock_if_mine with the lock owner details from the failed result. "
|
|
46
56
|
"Do not handcraft internal solution payloads or rely on build_id/stage/repair. "
|
|
47
57
|
"If the current MCP capability is unsupported, the workflow is awkward, or the user's need still cannot be satisfied after reasonable use, first summarize the gap, ask whether to submit feedback, and call feedback_submit only after explicit user confirmation."
|
|
@@ -169,6 +179,14 @@ def build_builder_server() -> FastMCP:
|
|
|
169
179
|
def builder_tool_contract(tool_name: str = "") -> dict:
|
|
170
180
|
return ai_builder.builder_tool_contract(tool_name=tool_name)
|
|
171
181
|
|
|
182
|
+
@server.tool()
|
|
183
|
+
def workspace_icon_catalog_get(profile: str = DEFAULT_PROFILE) -> dict:
|
|
184
|
+
return ai_builder.workspace_icon_catalog_get(profile=profile)
|
|
185
|
+
|
|
186
|
+
@server.tool()
|
|
187
|
+
def package_list(profile: str = DEFAULT_PROFILE, trial_status: str = "all", query: str = "") -> dict:
|
|
188
|
+
return ai_builder.package_list(profile=profile, trial_status=trial_status, query=query)
|
|
189
|
+
|
|
172
190
|
@server.tool()
|
|
173
191
|
def package_get(profile: str = DEFAULT_PROFILE, package_id: int = 0) -> dict:
|
|
174
192
|
return ai_builder.package_get(profile=profile, package_id=package_id)
|
|
@@ -295,29 +313,42 @@ def build_builder_server() -> FastMCP:
|
|
|
295
313
|
return ai_builder.button_style_catalog_get(profile=profile)
|
|
296
314
|
|
|
297
315
|
@server.tool()
|
|
298
|
-
def
|
|
299
|
-
return ai_builder.app_custom_button_list(profile=profile, app_key=app_key)
|
|
300
|
-
|
|
301
|
-
@server.tool()
|
|
302
|
-
def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> dict:
|
|
303
|
-
return ai_builder.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
|
|
304
|
-
|
|
305
|
-
@server.tool()
|
|
306
|
-
def app_custom_button_create(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: dict | None = None) -> dict:
|
|
307
|
-
return ai_builder.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
|
|
308
|
-
|
|
309
|
-
@server.tool()
|
|
310
|
-
def app_custom_button_update(
|
|
316
|
+
def app_custom_buttons_apply(
|
|
311
317
|
profile: str = DEFAULT_PROFILE,
|
|
312
318
|
app_key: str = "",
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
upsert_buttons: list[dict] | None = None,
|
|
320
|
+
patch_buttons: list[dict] | None = None,
|
|
321
|
+
remove_buttons: list[dict] | None = None,
|
|
322
|
+
view_configs: list[dict] | None = None,
|
|
315
323
|
) -> dict:
|
|
316
|
-
return ai_builder.
|
|
324
|
+
return ai_builder.app_custom_buttons_apply(
|
|
325
|
+
profile=profile,
|
|
326
|
+
app_key=app_key,
|
|
327
|
+
upsert_buttons=upsert_buttons or [],
|
|
328
|
+
patch_buttons=patch_buttons or [],
|
|
329
|
+
remove_buttons=remove_buttons or [],
|
|
330
|
+
view_configs=view_configs or [],
|
|
331
|
+
)
|
|
317
332
|
|
|
318
333
|
@server.tool()
|
|
319
|
-
def
|
|
320
|
-
|
|
334
|
+
def app_associated_resources_apply(
|
|
335
|
+
profile: str = DEFAULT_PROFILE,
|
|
336
|
+
app_key: str = "",
|
|
337
|
+
upsert_resources: list[dict] | None = None,
|
|
338
|
+
patch_resources: list[dict] | None = None,
|
|
339
|
+
remove_associated_item_ids: list[int] | None = None,
|
|
340
|
+
reorder_associated_item_ids: list[int] | None = None,
|
|
341
|
+
view_configs: list[dict] | None = None,
|
|
342
|
+
) -> dict:
|
|
343
|
+
return ai_builder.app_associated_resources_apply(
|
|
344
|
+
profile=profile,
|
|
345
|
+
app_key=app_key,
|
|
346
|
+
upsert_resources=upsert_resources or [],
|
|
347
|
+
patch_resources=patch_resources or [],
|
|
348
|
+
remove_associated_item_ids=remove_associated_item_ids or [],
|
|
349
|
+
reorder_associated_item_ids=reorder_associated_item_ids or [],
|
|
350
|
+
view_configs=view_configs or [],
|
|
351
|
+
)
|
|
321
352
|
|
|
322
353
|
@server.tool()
|
|
323
354
|
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|
|
@@ -387,7 +418,30 @@ def build_builder_server() -> FastMCP:
|
|
|
387
418
|
add_fields: list[dict] | None = None,
|
|
388
419
|
update_fields: list[dict] | None = None,
|
|
389
420
|
remove_fields: list[dict] | None = None,
|
|
421
|
+
apps: list[dict] | None = None,
|
|
390
422
|
) -> dict:
|
|
423
|
+
if apps:
|
|
424
|
+
if app_key or app_name or app_title or add_fields or update_fields or remove_fields:
|
|
425
|
+
return _config_failure(
|
|
426
|
+
"app_schema_apply multi-app mode accepts package_id/create_if_missing plus apps only.",
|
|
427
|
+
fix_hint="Use `apps` for batch mode, or use the single-app arguments without `apps`.",
|
|
428
|
+
)
|
|
429
|
+
if package_id is None:
|
|
430
|
+
return _config_failure(
|
|
431
|
+
"app_schema_apply multi-app mode requires package_id.",
|
|
432
|
+
fix_hint="Pass `package_id` and `apps[].app_name` for new apps, or `apps[].app_key` for existing apps.",
|
|
433
|
+
)
|
|
434
|
+
return ai_builder.app_schema_apply(
|
|
435
|
+
profile=profile,
|
|
436
|
+
package_id=package_id,
|
|
437
|
+
visibility=visibility,
|
|
438
|
+
create_if_missing=create_if_missing,
|
|
439
|
+
publish=publish,
|
|
440
|
+
add_fields=[],
|
|
441
|
+
update_fields=[],
|
|
442
|
+
remove_fields=[],
|
|
443
|
+
apps=apps,
|
|
444
|
+
)
|
|
391
445
|
has_app_key = bool((app_key or "").strip())
|
|
392
446
|
has_app_name = bool((app_name or "").strip())
|
|
393
447
|
has_app_title = bool((app_title or "").strip())
|
|
@@ -417,6 +471,7 @@ def build_builder_server() -> FastMCP:
|
|
|
417
471
|
add_fields=add_fields or [],
|
|
418
472
|
update_fields=update_fields or [],
|
|
419
473
|
remove_fields=remove_fields or [],
|
|
474
|
+
apps=[],
|
|
420
475
|
)
|
|
421
476
|
|
|
422
477
|
@server.tool()
|
|
@@ -429,22 +484,30 @@ def build_builder_server() -> FastMCP:
|
|
|
429
484
|
) -> dict:
|
|
430
485
|
return ai_builder.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [])
|
|
431
486
|
|
|
487
|
+
@server.tool()
|
|
488
|
+
def app_flow_get(profile: str = DEFAULT_PROFILE, app_key: str = "", version_id: str = "") -> dict:
|
|
489
|
+
return ai_builder.app_get_flow(profile=profile, app_key=app_key, version_id=version_id or None)
|
|
490
|
+
|
|
491
|
+
@server.tool()
|
|
492
|
+
def app_flow_get_schema(profile: str = DEFAULT_PROFILE, schema_version: str = "") -> dict:
|
|
493
|
+
return ai_builder.app_flow_get_schema(profile=profile, schema_version=schema_version or None)
|
|
494
|
+
|
|
432
495
|
@server.tool()
|
|
433
496
|
def app_flow_apply(
|
|
434
497
|
profile: str = DEFAULT_PROFILE,
|
|
435
498
|
app_key: str = "",
|
|
436
|
-
mode: str = "replace",
|
|
437
499
|
publish: bool = True,
|
|
438
|
-
|
|
439
|
-
|
|
500
|
+
spec: dict | None = None,
|
|
501
|
+
idempotency_key: str = "",
|
|
502
|
+
schema_version: str = "",
|
|
440
503
|
) -> dict:
|
|
441
504
|
return ai_builder.app_flow_apply(
|
|
442
505
|
profile=profile,
|
|
443
506
|
app_key=app_key,
|
|
444
|
-
mode=mode,
|
|
445
507
|
publish=publish,
|
|
446
|
-
|
|
447
|
-
|
|
508
|
+
spec=spec or {},
|
|
509
|
+
idempotency_key=idempotency_key or None,
|
|
510
|
+
schema_version=schema_version or None,
|
|
448
511
|
)
|
|
449
512
|
|
|
450
513
|
@server.tool()
|
|
@@ -453,6 +516,7 @@ def build_builder_server() -> FastMCP:
|
|
|
453
516
|
app_key: str = "",
|
|
454
517
|
publish: bool = True,
|
|
455
518
|
upsert_views: list[dict] | None = None,
|
|
519
|
+
patch_views: list[dict] | None = None,
|
|
456
520
|
remove_views: list[str] | None = None,
|
|
457
521
|
) -> dict:
|
|
458
522
|
return ai_builder.app_views_apply(
|
|
@@ -460,6 +524,7 @@ def build_builder_server() -> FastMCP:
|
|
|
460
524
|
app_key=app_key,
|
|
461
525
|
publish=publish,
|
|
462
526
|
upsert_views=upsert_views or [],
|
|
527
|
+
patch_views=patch_views or [],
|
|
463
528
|
remove_views=remove_views or [],
|
|
464
529
|
)
|
|
465
530
|
|
|
@@ -468,6 +533,7 @@ def build_builder_server() -> FastMCP:
|
|
|
468
533
|
profile: str = DEFAULT_PROFILE,
|
|
469
534
|
app_key: str = "",
|
|
470
535
|
upsert_charts: list[dict] | None = None,
|
|
536
|
+
patch_charts: list[dict] | None = None,
|
|
471
537
|
remove_chart_ids: list[str] | None = None,
|
|
472
538
|
reorder_chart_ids: list[str] | None = None,
|
|
473
539
|
) -> dict:
|
|
@@ -475,6 +541,7 @@ def build_builder_server() -> FastMCP:
|
|
|
475
541
|
profile=profile,
|
|
476
542
|
app_key=app_key,
|
|
477
543
|
upsert_charts=upsert_charts or [],
|
|
544
|
+
patch_charts=patch_charts or [],
|
|
478
545
|
remove_chart_ids=remove_chart_ids or [],
|
|
479
546
|
reorder_chart_ids=reorder_chart_ids or [],
|
|
480
547
|
)
|
|
@@ -484,9 +551,12 @@ def build_builder_server() -> FastMCP:
|
|
|
484
551
|
profile: str = DEFAULT_PROFILE,
|
|
485
552
|
dash_key: str = "",
|
|
486
553
|
dash_name: str = "",
|
|
554
|
+
name: str = "",
|
|
487
555
|
package_id: int | None = None,
|
|
488
556
|
publish: bool = True,
|
|
489
557
|
sections: list[dict] | None = None,
|
|
558
|
+
pages: list[dict] | None = None,
|
|
559
|
+
layout_preset: str = "",
|
|
490
560
|
visibility: dict | None = None,
|
|
491
561
|
auth: dict | None = None,
|
|
492
562
|
icon: str | None = None,
|
|
@@ -494,10 +564,14 @@ def build_builder_server() -> FastMCP:
|
|
|
494
564
|
hide_copyright: bool | None = None,
|
|
495
565
|
dash_global_config: dict | None = None,
|
|
496
566
|
config: dict | None = None,
|
|
567
|
+
payload: dict | None = None,
|
|
497
568
|
) -> dict:
|
|
569
|
+
payload = payload if isinstance(payload, dict) else {}
|
|
498
570
|
has_dash_key = bool((dash_key or "").strip())
|
|
499
|
-
|
|
500
|
-
|
|
571
|
+
effective_dash_name = (dash_name or name or str(payload.get("dash_name") or payload.get("dashName") or payload.get("name") or "")).strip()
|
|
572
|
+
has_dash_name = bool(effective_dash_name)
|
|
573
|
+
effective_package_id = package_id if package_id is not None else payload.get("package_id") or payload.get("packageId") or payload.get("package_tag_id")
|
|
574
|
+
has_package_id = effective_package_id is not None
|
|
501
575
|
if has_dash_key and has_package_id:
|
|
502
576
|
return _config_failure(
|
|
503
577
|
"portal_apply accepts exactly one selector mode.",
|
|
@@ -512,9 +586,12 @@ def build_builder_server() -> FastMCP:
|
|
|
512
586
|
profile=profile,
|
|
513
587
|
dash_key=dash_key,
|
|
514
588
|
dash_name=dash_name,
|
|
589
|
+
name=name,
|
|
515
590
|
package_id=package_id,
|
|
516
591
|
publish=publish,
|
|
517
592
|
sections=sections or [],
|
|
593
|
+
pages=pages or [],
|
|
594
|
+
layout_preset=layout_preset,
|
|
518
595
|
visibility=visibility,
|
|
519
596
|
auth=auth,
|
|
520
597
|
icon=icon,
|
|
@@ -522,6 +599,7 @@ def build_builder_server() -> FastMCP:
|
|
|
522
599
|
hide_copyright=hide_copyright,
|
|
523
600
|
dash_global_config=dash_global_config,
|
|
524
601
|
config=config or {},
|
|
602
|
+
payload=payload,
|
|
525
603
|
)
|
|
526
604
|
|
|
527
605
|
@server.tool()
|