@josephyan/qingflow-app-builder-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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-app-builder-mcp@1.1.2
6
+ npm install @josephyan/qingflow-app-builder-mcp@1.1.3
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-builder-mcp@1.1.2 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@1.1.3 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.1.2"
7
+ version = "1.1.3"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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", required=True)
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 targeted section 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",
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",