@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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +8 -0
- package/skills/qingflow-app-builder/references/create-app.md +39 -2
- package/skills/qingflow-app-builder/references/tool-selection.md +2 -2
- package/skills/qingflow-app-builder/references/update-views.md +2 -1
- package/src/qingflow_mcp/builder_facade/models.py +43 -2
- package/src/qingflow_mcp/builder_facade/service.py +1476 -172
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +68 -13
- package/src/qingflow_mcp/cli/commands/record.py +16 -1
- package/src/qingflow_mcp/cli/formatters.py +32 -1
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/public_surface.py +3 -1
- package/src/qingflow_mcp/response_trim.py +70 -13
- package/src/qingflow_mcp/server.py +10 -9
- package/src/qingflow_mcp/server_app_builder.py +53 -7
- package/src/qingflow_mcp/server_app_user.py +12 -15
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1642 -75
- 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 +1423 -70
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
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.
|
|
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.
|
|
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
package/pyproject.toml
CHANGED
|
@@ -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
|
|
41
|
-
3.
|
|
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) ->
|
|
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
|
-
-
|
|
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=
|
|
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)
|