@qingflow-tech/qingflow-app-builder-mcp 1.0.9 → 1.0.11

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 @qingflow-tech/qingflow-app-builder-mcp@1.0.9
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.11
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.9 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.11 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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.0.9"
7
+ version = "1.0.11"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -49,6 +49,9 @@ Treat these as the official surface. Do not default to `package_create`, `packag
49
49
  - Package work:
50
50
  - use `package_get(package_id=...)` to read one known package
51
51
  - use `package_apply(...)` for package creation, rename, icon, visibility, grouping, ordering, and app/portal layout
52
+ - Multi-app schema work:
53
+ - use one `app_schema_apply(apps=[...])` / CLI `builder schema apply --apps-file` when creating several apps in one package
54
+ - same-call relation fields may use `target_app_ref` to point at another `apps[].client_key`
52
55
  - App base permissions:
53
56
  - trust `app_get.editability.can_edit_app_base` for app base-info writes like app name, icon, and visibility
54
57
  - `can_edit_form` only means schema/form-route capability; it no longer implies app base-info write capability
@@ -97,6 +100,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
97
100
  - builder view writes use raw `view_key` values from `app_get.views`; `custom:<viewKey>` is a record-data `view_id` form, not a builder `view_key`
98
101
  - use `builder_tool_contract` whenever the minimal legal shape is unclear
99
102
  - keep view `filters` and `query_conditions` separate: `filters` are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
103
+ - new views default the associated report/view display area to visible with `limit_type="all"`; existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched
100
104
  - configure associated reports/views through `app_associated_resources_apply`, not through `app_views_apply`
101
105
 
102
106
  ## Standard Operating Order
@@ -145,6 +149,10 @@ Treat these as the official surface. Do not default to `package_create`, `packag
145
149
 
146
150
  ## Response Interpretation
147
151
 
152
+ - All builder apply/write tools return a standard UI envelope in addition to legacy fields:
153
+ `schema_version`, `operation`, `summary`, and `resources[]`.
154
+ - For UI cards or quick narration, read `resources[]` first. Each resource has `resource_type`, `operation`, `status`, `id`, `key`, `name`, typed `ids`, and `parent`.
155
+ - Use legacy fields such as `field_diff`, `views_diff`, `chart_results`, `created/updated/removed`, and `verification` only for compatibility and troubleshooting.
148
156
  - Treat post-write readback as the source of truth, not just write status codes.
149
157
  - `success` means write and verification completed; `partial_success` means the write landed but verification is incomplete.
150
158
  - For portals, distinguish clearly between:
@@ -37,8 +37,8 @@ then do not treat that as one app.
37
37
  Use this pattern instead:
38
38
 
39
39
  1. `package_get` or `package_apply(create_if_missing=true, package_name=...)`
40
- 2. for each app name, run `app_schema_apply`
41
- 3. once the apps exist, add `relation` fields between them
40
+ 2. for a multi-app system, run one `app_schema_apply` with `apps[]`
41
+ 3. use `apps[].client_key` plus relation field `target_app_ref` when one new app references another new app
42
42
 
43
43
  ## Example
44
44
 
@@ -91,6 +91,43 @@ Apply schema for a new app:
91
91
  }
92
92
  ```
93
93
 
94
+ Apply schema for multiple apps in one call:
95
+
96
+ ```json
97
+ {
98
+ "tool_name": "app_schema_apply",
99
+ "arguments": {
100
+ "profile": "default",
101
+ "package_id": 1218950,
102
+ "create_if_missing": true,
103
+ "publish": true,
104
+ "apps": [
105
+ {
106
+ "client_key": "customer",
107
+ "app_name": "客户",
108
+ "add_fields": [
109
+ {"name": "客户名称", "type": "text", "required": true, "as_data_title": true}
110
+ ]
111
+ },
112
+ {
113
+ "client_key": "order",
114
+ "app_name": "订单",
115
+ "add_fields": [
116
+ {"name": "订单编号", "type": "text", "required": true, "as_data_title": true},
117
+ {
118
+ "name": "关联客户",
119
+ "type": "relation",
120
+ "target_app_ref": "customer",
121
+ "display_field": {"name": "客户名称"},
122
+ "visible_fields": [{"name": "客户名称"}]
123
+ }
124
+ ]
125
+ }
126
+ ]
127
+ }
128
+ }
129
+ ```
130
+
94
131
  Data title is required: mark exactly one top-level field with `as_data_title: true`. Data cover is optional and only valid on a top-level `attachment` field.
95
132
 
96
133
  ## Common failures
@@ -45,7 +45,7 @@ These execute normalized patches. Some app apply tools publish by default and st
45
45
  - `app_schema_apply`: create app shell or change fields
46
46
  - `app_layout_apply`: merge or replace layout
47
47
  - `app_flow_apply`: replace workflow
48
- - `app_views_apply`: use `patch_views` for existing-view parameter replacement; use `upsert_views` for creation or full target config; remove views by key
48
+ - `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"`
49
49
  - `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.
50
50
  - `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.
51
51
  - `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
@@ -64,7 +64,7 @@ For object-level updates, the safe partial syntax is `patch_*` with the object's
64
64
  - Create a brand new package, then create one app in it:
65
65
  `package_apply(create_if_missing=true) -> app_schema_apply`
66
66
  - Create a brand new multi-app system/package:
67
- `package_apply(create_if_missing=true) -> per-app app_schema_apply -> relation field patches`
67
+ `package_apply(create_if_missing=true) -> app_schema_apply(apps[])`
68
68
  - Update fields on an existing app:
69
69
  `app_resolve -> app_get_fields -> app_schema_apply`
70
70
  - Tidy layout:
@@ -30,7 +30,8 @@ Canonical rules before any example:
30
30
  - Treat `fields` only as a legacy alias the MCP may normalize, not as the preferred shape
31
31
  - Use `filters` with canonical keys `field_name`, `operator`, `value`/`values`
32
32
  - Use `query_conditions` for the frontend query panel. Do not put query-panel fields into `filters`.
33
- - Use `app_associated_resources_apply` for the frontend associated report/view area. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
33
+ - New views created by `app_views_apply` default the frontend associated report/view area to visible with `limit_type="all"`. Existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched.
34
+ - Use `app_associated_resources_apply` for the associated report/view resource pool, selected resources, and match rules. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
34
35
  - For gantt, use `start_field`, `end_field`, and optionally `title_field`
35
36
  - If `app_get.views` or `app_get_views` shows duplicate view names, include `view_key` in `upsert_views[]` and update that exact target
36
37
  - Builder view writes always use the raw `view_key` from `app_get.views[].view_key`, such as `emsrao25rs02`. Do not pass `custom:emsrao25rs02`; that prefixed form is only for record-data `view_id`.
@@ -1893,8 +1893,9 @@ class PortalComponentPositionPatch(StrictModel):
1893
1893
  pc_h: int = Field(default=8, validation_alias=AliasChoices("pc_h", "pcH", "h"))
1894
1894
  mobile_x: int = Field(default=0, validation_alias=AliasChoices("mobile_x", "mobileX"))
1895
1895
  mobile_y: int = Field(default=0, validation_alias=AliasChoices("mobile_y", "mobileY"))
1896
- mobile_w: int = Field(default=12, validation_alias=AliasChoices("mobile_w", "mobileW"))
1896
+ mobile_w: int = Field(default=6, validation_alias=AliasChoices("mobile_w", "mobileW"))
1897
1897
  mobile_h: int = Field(default=8, validation_alias=AliasChoices("mobile_h", "mobileH"))
1898
+ mobile_provided: bool = Field(default=False, exclude=True)
1898
1899
 
1899
1900
  @model_validator(mode="before")
1900
1901
  @classmethod
@@ -1904,6 +1905,8 @@ class PortalComponentPositionPatch(StrictModel):
1904
1905
  payload = dict(value)
1905
1906
  pc = payload.pop("pc", None)
1906
1907
  mobile = payload.pop("mobile", None)
1908
+ mobile_keys = {"mobile_x", "mobileX", "mobile_y", "mobileY", "mobile_w", "mobileW", "mobile_h", "mobileH"}
1909
+ mobile_provided = isinstance(mobile, dict) or any(key in payload for key in mobile_keys)
1907
1910
  if isinstance(pc, dict):
1908
1911
  if "pc_x" not in payload and "x" in pc:
1909
1912
  payload["pc_x"] = pc.get("x")
@@ -1922,6 +1925,7 @@ class PortalComponentPositionPatch(StrictModel):
1922
1925
  payload["mobile_w"] = mobile.get("cols")
1923
1926
  if "mobile_h" not in payload and "rows" in mobile:
1924
1927
  payload["mobile_h"] = mobile.get("rows")
1928
+ payload["mobile_provided"] = mobile_provided
1925
1929
  return payload
1926
1930
 
1927
1931
 
@@ -2003,9 +2007,10 @@ class PortalSectionPatch(StrictModel):
2003
2007
  class PortalApplyRequest(StrictModel):
2004
2008
  dash_key: str | None = None
2005
2009
  dash_name: str | None = None
2006
- package_tag_id: int | None = None
2010
+ package_tag_id: int | None = Field(default=None, validation_alias=AliasChoices("package_tag_id", "packageTagId", "package_id", "packageId"))
2007
2011
  publish: bool = True
2008
2012
  sections: list[PortalSectionPatch] = Field(default_factory=list)
2013
+ layout_preset: str | None = Field(default=None, validation_alias=AliasChoices("layout_preset", "layoutPreset"))
2009
2014
  visibility: VisibilityPatch | None = None
2010
2015
  auth: dict[str, Any] | None = None
2011
2016
  icon: str | None = None
@@ -2014,6 +2019,33 @@ class PortalApplyRequest(StrictModel):
2014
2019
  dash_global_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_global_config", "dashGlobalConfig"))
2015
2020
  config: dict[str, Any] = Field(default_factory=dict)
2016
2021
 
2022
+ @model_validator(mode="before")
2023
+ @classmethod
2024
+ def normalize_compat_payload(cls, value: Any) -> Any:
2025
+ if not isinstance(value, dict):
2026
+ return value
2027
+ payload = dict(value)
2028
+ if "dash_name" not in payload and "dashName" not in payload and "name" in payload:
2029
+ payload["dash_name"] = payload.pop("name")
2030
+ if "sections" not in payload and "pages" in payload:
2031
+ pages = payload.pop("pages")
2032
+ if not isinstance(pages, list):
2033
+ raise ValueError("portal pages must be a list")
2034
+ if len(pages) != 1:
2035
+ raise ValueError("portal_apply currently supports a single page; pass one page or flatten components into sections")
2036
+ page = pages[0]
2037
+ if not isinstance(page, dict):
2038
+ raise ValueError("portal pages[0] must be an object")
2039
+ components = page.get("components")
2040
+ if not isinstance(components, list) or not components:
2041
+ raise ValueError("portal pages[0].components must be a non-empty list")
2042
+ payload["sections"] = components
2043
+ if "theme" in payload:
2044
+ payload.pop("theme")
2045
+ if "type" in payload:
2046
+ payload.pop("type")
2047
+ return payload
2048
+
2017
2049
  @model_validator(mode="after")
2018
2050
  def validate_shape(self) -> "PortalApplyRequest":
2019
2051
  if not self.dash_key and not self.package_tag_id:
@@ -2024,6 +2056,8 @@ class PortalApplyRequest(StrictModel):
2024
2056
  raise ValueError("portal apply requires a non-empty sections list when creating a portal")
2025
2057
  if self.visibility is not None and self.auth is not None:
2026
2058
  raise ValueError("visibility and auth cannot be provided together")
2059
+ if self.layout_preset is not None and self.layout_preset not in {"auto", "dashboard_2col", "dashboard_3col"}:
2060
+ raise ValueError("layout_preset must be one of: auto, dashboard_2col, dashboard_3col")
2027
2061
  return self
2028
2062
 
2029
2063
 
@@ -2034,8 +2068,11 @@ FieldUpdatePatch.model_rebuild()
2034
2068
 
2035
2069
  class AppGetResponse(StrictModel):
2036
2070
  app_key: str
2071
+ app_name: str | None = None
2072
+ name: str | None = None
2037
2073
  title: str | None = None
2038
2074
  app_icon: str | None = None
2075
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2039
2076
  visibility: dict[str, Any] = Field(default_factory=dict)
2040
2077
  tag_ids: list[int] = Field(default_factory=list)
2041
2078
  publish_status: int | None = None
@@ -2060,6 +2097,8 @@ class AppGetFieldsResponse(StrictModel):
2060
2097
  app_key: str
2061
2098
  fields: list[dict[str, Any]] = Field(default_factory=list)
2062
2099
  field_count: int = 0
2100
+ chart_fields: list[dict[str, Any]] = Field(default_factory=list)
2101
+ chart_field_count: int = 0
2063
2102
  form_settings: dict[str, Any] = Field(default_factory=dict)
2064
2103
 
2065
2104
 
@@ -2107,6 +2146,7 @@ class PortalReadSummaryResponse(StrictModel):
2107
2146
  dash_name: str | None = None
2108
2147
  package_tag_ids: list[int] = Field(default_factory=list)
2109
2148
  dash_icon: str | None = None
2149
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2110
2150
  hide_copyright: bool | None = None
2111
2151
  config_keys: list[str] = Field(default_factory=list)
2112
2152
  dash_global_config_keys: list[str] = Field(default_factory=list)
@@ -2120,6 +2160,7 @@ class PortalGetResponse(StrictModel):
2120
2160
  dash_name: str | None = None
2121
2161
  package_tag_ids: list[int] = Field(default_factory=list)
2122
2162
  dash_icon: str | None = None
2163
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2123
2164
  hide_copyright: bool | None = None
2124
2165
  visibility: dict[str, Any] = Field(default_factory=dict)
2125
2166
  auth: dict[str, Any] = Field(default_factory=dict)