@josephyan/qingflow-app-user-mcp 1.1.2 → 1.1.3
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +1 -0
- package/skills/qingflow-app-builder/references/tool-selection.md +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +24 -0
- package/src/qingflow_mcp/cli/commands/builder.py +32 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +37 -1
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@1.1.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@1.1.3
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@1.1.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@1.1.3 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -87,6 +87,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
|
|
|
87
87
|
- advanced bindings may use `button_limit`, `button_formula`, `button_formula_type`, and `print_tpls` only when visibility or print-template behavior is required
|
|
88
88
|
- Associated resources:
|
|
89
89
|
- `app_associated_resources_apply` is the public write path for the Qingflow app-level associated report/view pool and per-view display config
|
|
90
|
+
- for multi-app systems, use `apps[]` to batch associated-resource writes; each item carries its own `app_key` plus `upsert_resources` / `patch_resources` / `view_configs`
|
|
90
91
|
- it attaches existing BI reports/views for in-app display; it does not create or edit QingBI report bodies/configs, including dataset reports
|
|
91
92
|
- use `patch_resources` for changing match rules or other existing associated-resource parameters; the tool preserves backend-required raw fields internally
|
|
92
93
|
- `associated_item_id` is the backend internal associated-resource id; for `view_configs`, `remove_associated_item_ids`, and `reorder_associated_item_ids`, you may pass `associated_item_id` or an existing resource's `chart_id`/`chart_key`/`view_key`, and the tool resolves it to the internal id
|
|
@@ -52,7 +52,7 @@ These execute normalized patches. Some app apply tools publish by default and st
|
|
|
52
52
|
- `app_flow_apply`: replace full workflow spec; use `patch_nodes[]` (`{id, set, unset}`) to update specific nodes without rewriting the full spec
|
|
53
53
|
- `app_views_apply`: use `patch_views` for existing-view parameter replacement; use `upsert_views` for creation or full target config; remove views by key; new views default associated report/view display to visible with `limit_type="all"`; use `apps[]` for multi-app batch
|
|
54
54
|
- `app_custom_buttons_apply`: use `patch_buttons` for existing-button parameter replacement; use `upsert_buttons` for creation or full target config; configure add-data field mappings/default values; bind buttons to header/detail/list view positions; `placement=list` maps to the backend `INSIDE` row/list button position; merge-mode view configs require `buttons`; use `view_configs[].mode="replace"` or `buttons=[]` to clear a view's custom button bindings. For child-record creation linked to the current source record, map `source_field: "数据ID"` to the target relation field. Use `apps[]` for multi-app batch.
|
|
55
|
-
- `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
|
|
55
|
+
- `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call. Use `apps[]` for multi-app batch.
|
|
56
56
|
- `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique; use `apps[]` for multi-app batch
|
|
57
57
|
- `portal_apply`: create or replace-update portal pages; use `dash_key` for update mode or `package_id + dash_name` for create mode; edit mode may omit `sections` for base-info-only updates; when sections are supplied they still use replace semantics; sections use a 24-column PC grid — all components in the same row (same `y`) must share the same `rows` value and their `cols` must sum to exactly 24; mismatched `rows` causes height misalignment, `cols` under 24 leaves trailing blank space; use `patch_sections[]` (`{chart_ref/view_ref/order, set, unset}`) to update individual sections without replacing all
|
|
58
58
|
|
|
@@ -5486,6 +5486,9 @@ class AiBuilderFacade:
|
|
|
5486
5486
|
result = []
|
|
5487
5487
|
for c in components:
|
|
5488
5488
|
item = {k: v for k, v in c.items() if k != "order"}
|
|
5489
|
+
position = item.get("position")
|
|
5490
|
+
if isinstance(position, dict):
|
|
5491
|
+
item["position"] = _portal_component_position_for_request(position)
|
|
5489
5492
|
result.append(item)
|
|
5490
5493
|
return result
|
|
5491
5494
|
|
|
@@ -14180,6 +14183,27 @@ def _portal_position_payload(position: Any, *, inferred_mobile_y: int = 0) -> di
|
|
|
14180
14183
|
}
|
|
14181
14184
|
|
|
14182
14185
|
|
|
14186
|
+
def _portal_component_position_for_request(position: dict[str, Any]) -> dict[str, Any]:
|
|
14187
|
+
"""Normalize portal_get component positions into PortalSectionPatch input shape."""
|
|
14188
|
+
payload = deepcopy(position)
|
|
14189
|
+
if isinstance(payload.get("pc"), dict) or isinstance(payload.get("mobile"), dict):
|
|
14190
|
+
return payload
|
|
14191
|
+
pc_keys = {"x", "y", "cols", "rows"}
|
|
14192
|
+
if any(key in payload for key in pc_keys):
|
|
14193
|
+
pc = {
|
|
14194
|
+
"x": int(payload.get("x") or 0),
|
|
14195
|
+
"y": int(payload.get("y") or 0),
|
|
14196
|
+
"cols": int(payload.get("cols") or payload.get("w") or 12),
|
|
14197
|
+
"rows": int(payload.get("rows") or payload.get("h") or 8),
|
|
14198
|
+
}
|
|
14199
|
+
normalized: dict[str, Any] = {"pc": pc}
|
|
14200
|
+
mobile = payload.get("mobile")
|
|
14201
|
+
if isinstance(mobile, dict):
|
|
14202
|
+
normalized["mobile"] = mobile
|
|
14203
|
+
return normalized
|
|
14204
|
+
return payload
|
|
14205
|
+
|
|
14206
|
+
|
|
14183
14207
|
def _portal_component_position_public(
|
|
14184
14208
|
source_type: Any,
|
|
14185
14209
|
*,
|
|
@@ -166,12 +166,13 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
166
166
|
associated_resource_get.set_defaults(handler=_handle_associated_resource_get, format_hint="builder_summary")
|
|
167
167
|
|
|
168
168
|
associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
|
|
169
|
-
associated_resource_apply.add_argument("--app-key",
|
|
169
|
+
associated_resource_apply.add_argument("--app-key", default="")
|
|
170
170
|
associated_resource_apply.add_argument("--upsert-resources-file")
|
|
171
171
|
associated_resource_apply.add_argument("--patch-resources-file")
|
|
172
172
|
associated_resource_apply.add_argument("--remove-associated-item-ids-file")
|
|
173
173
|
associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
|
|
174
174
|
associated_resource_apply.add_argument("--view-configs-file")
|
|
175
|
+
associated_resource_apply.add_argument("--apps-file", help="多应用批量关联资源 JSON 数组,每项含 app_key + upsert_resources/patch_resources/remove_associated_item_ids/reorder_associated_item_ids/view_configs")
|
|
175
176
|
associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary", force_json_output=True)
|
|
176
177
|
|
|
177
178
|
portal = builder_subparsers.add_parser("portal", help="门户")
|
|
@@ -456,6 +457,36 @@ def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
456
457
|
|
|
457
458
|
|
|
458
459
|
def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
460
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
461
|
+
if apps:
|
|
462
|
+
single_app_args = [
|
|
463
|
+
a for a in [
|
|
464
|
+
"--app-key" if args.app_key else None,
|
|
465
|
+
"--upsert-resources-file" if getattr(args, "upsert_resources_file", None) else None,
|
|
466
|
+
"--patch-resources-file" if getattr(args, "patch_resources_file", None) else None,
|
|
467
|
+
"--remove-associated-item-ids-file" if getattr(args, "remove_associated_item_ids_file", None) else None,
|
|
468
|
+
"--reorder-associated-item-ids-file" if getattr(args, "reorder_associated_item_ids_file", None) else None,
|
|
469
|
+
"--view-configs-file" if getattr(args, "view_configs_file", None) else None,
|
|
470
|
+
] if a
|
|
471
|
+
]
|
|
472
|
+
if single_app_args:
|
|
473
|
+
raise_config_error(
|
|
474
|
+
f"associated-resource apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
475
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
|
|
476
|
+
)
|
|
477
|
+
if apps:
|
|
478
|
+
return context.builder.app_associated_resources_apply(
|
|
479
|
+
profile=args.profile,
|
|
480
|
+
app_key="",
|
|
481
|
+
upsert_resources=[],
|
|
482
|
+
patch_resources=[],
|
|
483
|
+
remove_associated_item_ids=[],
|
|
484
|
+
reorder_associated_item_ids=[],
|
|
485
|
+
view_configs=[],
|
|
486
|
+
apps=apps,
|
|
487
|
+
)
|
|
488
|
+
if not args.app_key:
|
|
489
|
+
raise_config_error("associated-resource apply requires --app-key or --apps-file", fix_hint="Pass --app-key APP_KEY for single-app mode, or --apps-file for batch mode.")
|
|
459
490
|
return context.builder.app_associated_resources_apply(
|
|
460
491
|
profile=args.profile,
|
|
461
492
|
app_key=args.app_key,
|
|
@@ -284,6 +284,7 @@ class AiBuilderTools(ToolBase):
|
|
|
284
284
|
remove_associated_item_ids: list[int] | None = None,
|
|
285
285
|
reorder_associated_item_ids: list[int] | None = None,
|
|
286
286
|
view_configs: list[JSONObject] | None = None,
|
|
287
|
+
apps: list[JSONObject] | None = None,
|
|
287
288
|
) -> JSONObject:
|
|
288
289
|
return self.app_associated_resources_apply(
|
|
289
290
|
profile=profile,
|
|
@@ -293,6 +294,7 @@ class AiBuilderTools(ToolBase):
|
|
|
293
294
|
remove_associated_item_ids=remove_associated_item_ids or [],
|
|
294
295
|
reorder_associated_item_ids=reorder_associated_item_ids or [],
|
|
295
296
|
view_configs=view_configs or [],
|
|
297
|
+
apps=apps,
|
|
296
298
|
)
|
|
297
299
|
|
|
298
300
|
@mcp.tool()
|
|
@@ -1120,8 +1122,24 @@ class AiBuilderTools(ToolBase):
|
|
|
1120
1122
|
reorder_associated_item_ids: list[int],
|
|
1121
1123
|
view_configs: list[JSONObject],
|
|
1122
1124
|
patch_resources: list[JSONObject] | None = None,
|
|
1125
|
+
apps: list[JSONObject] | None = None,
|
|
1123
1126
|
) -> JSONObject:
|
|
1124
1127
|
"""执行应用关联资源 apply 逻辑。"""
|
|
1128
|
+
if apps:
|
|
1129
|
+
return self._facade._batch_write_apps(
|
|
1130
|
+
profile=profile,
|
|
1131
|
+
apps=apps,
|
|
1132
|
+
single_writer=lambda profile, app_key, **kw: self.app_associated_resources_apply(
|
|
1133
|
+
profile=profile,
|
|
1134
|
+
app_key=app_key,
|
|
1135
|
+
upsert_resources=kw.get("upsert_resources", []),
|
|
1136
|
+
patch_resources=kw.get("patch_resources", []),
|
|
1137
|
+
remove_associated_item_ids=kw.get("remove_associated_item_ids", []),
|
|
1138
|
+
reorder_associated_item_ids=kw.get("reorder_associated_item_ids", []),
|
|
1139
|
+
view_configs=kw.get("view_configs", []),
|
|
1140
|
+
),
|
|
1141
|
+
tool_name="app_associated_resources_apply",
|
|
1142
|
+
)
|
|
1125
1143
|
raw_request = {
|
|
1126
1144
|
"app_key": app_key,
|
|
1127
1145
|
"upsert_resources": upsert_resources,
|
|
@@ -4458,6 +4476,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
4458
4476
|
"app_associated_resources_apply": {
|
|
4459
4477
|
"allowed_keys": [
|
|
4460
4478
|
"app_key",
|
|
4479
|
+
"apps",
|
|
4461
4480
|
"upsert_resources",
|
|
4462
4481
|
"patch_resources",
|
|
4463
4482
|
"remove_associated_item_ids",
|
|
@@ -4527,6 +4546,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
4527
4546
|
"if an associated resource delete returns readback_status=unavailable or still_exists, treat the result as readback pending and do not blindly repeat the delete",
|
|
4528
4547
|
"this tool publishes after at least one write succeeds; there is no draft-only mode",
|
|
4529
4548
|
"visible=false hides the associated-resource area without clearing previous selected ids; visible=true with limit_type=all shows the whole app-level pool",
|
|
4549
|
+
"accepts apps[] for multi-app batch; each item is {app_key, upsert_resources?, patch_resources?, remove_associated_item_ids?, reorder_associated_item_ids?, view_configs?}",
|
|
4530
4550
|
],
|
|
4531
4551
|
"minimal_example": {
|
|
4532
4552
|
"profile": "default",
|
|
@@ -4557,6 +4577,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
4557
4577
|
}
|
|
4558
4578
|
],
|
|
4559
4579
|
},
|
|
4580
|
+
"batch_example": {
|
|
4581
|
+
"profile": "default",
|
|
4582
|
+
"apps": [
|
|
4583
|
+
{
|
|
4584
|
+
"app_key": "APP_1",
|
|
4585
|
+
"upsert_resources": [
|
|
4586
|
+
{"client_key": "orders_view", "graph_type": "view", "target_app_key": "ORDER_APP", "view_key": "ORDER_VIEW"}
|
|
4587
|
+
],
|
|
4588
|
+
"view_configs": [{"view_key": "MAIN_VIEW", "limit_type": "select", "associated_item_refs": ["orders_view"]}],
|
|
4589
|
+
},
|
|
4590
|
+
{
|
|
4591
|
+
"app_key": "APP_2",
|
|
4592
|
+
"view_configs": [{"view_key": "MAIN_VIEW", "visible": True, "limit_type": "all"}],
|
|
4593
|
+
},
|
|
4594
|
+
],
|
|
4595
|
+
},
|
|
4560
4596
|
},
|
|
4561
4597
|
"app_schema_plan": {
|
|
4562
4598
|
"allowed_keys": ["app_key", "package_id", "app_name", "icon", "color", "visibility", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
|
|
@@ -5552,7 +5588,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5552
5588
|
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
5553
5589
|
"portal_apply uses replace semantics for sections",
|
|
5554
5590
|
"when editing an existing portal, sections may be omitted to update only base info such as visibility, icon, or package",
|
|
5555
|
-
"use patch_sections[] for
|
|
5591
|
+
"use patch_sections[] for section-level patch updates without replacing all sections; each item needs one selector (chart_ref with chart_id/chart_key/chart_name, view_ref with view_key/view_name, or order as 0-based index) plus set/unset",
|
|
5556
5592
|
"when sections[] is supplied without patch_sections[], it uses replace semantics for all sections",
|
|
5557
5593
|
"remove a section by omitting it from the sections list (replace mode) or by unset in patch_sections (patch mode)",
|
|
5558
5594
|
"package_id is required when creating a new portal",
|