@josephyan/qingflow-app-builder-mcp 0.2.0-beta.14 → 0.2.0-beta.16

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@0.2.0-beta.14
6
+ npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.16
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.14 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.16 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
@@ -20,7 +20,7 @@ Environment:
20
20
 
21
21
  This package bootstraps a local Python runtime on first install and then starts the `qingflow-app-builder-mcp` stdio MCP server.
22
22
 
23
- Bundled skill:
23
+ Bundled skills:
24
24
 
25
25
  - `skills/qingflow-app-builder`
26
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "0.2.0-beta.14",
3
+ "version": "0.2.0-beta.16",
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 = "0.2.0b14"
7
+ version = "0.2.0b16"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -152,6 +152,12 @@ For additive work on existing systems:
152
152
  - `permissions.editable_fields`
153
153
  - Reuse `app_flow_plan` output directly when it succeeds. Do not rewrite it into internal keys such as `role_entries` or `editable_que_ids`.
154
154
  - Reuse `app_views_plan` output directly when it succeeds. Do not re-expand aliases such as `column_names`.
155
+ - For layout work, keep the public section shape canonical:
156
+ - `title`
157
+ - `rows`
158
+ Do not invent top-level layout `columns`, and do not prefer `fields` or `field_ids` once `rows` is known.
159
+ - Translate natural language like “一行四个字段 / 四列布局 / 每行放四个” into `rows` matrices, not guessed layout parameters.
160
+ - If the same layout-shape `VALIDATION_ERROR` repeats twice, stop guessing and re-read `builder_tool_contract(app_layout_plan)` or the layout reference before trying again.
155
161
  - Do not guess role ids or member ids. Resolve them from the directory first.
156
162
  - `app_schema_apply` does not treat package attachment as success criteria; if package ownership matters, verify `tag_ids_after` and call `package_attach_app` explicitly.
157
163
  - `package_attach_app` is the source of truth for package ownership; do not assume app creation or publish implicitly attaches the app.
@@ -167,6 +173,8 @@ For additive work on existing systems:
167
173
  - If readback mismatches the UI, compare `request_route` and do not assume the builder hit the same `qf_version` as the browser
168
174
  - Treat post-write readback as the source of truth, not just write status codes
169
175
  - For views, a top-level `VIEW_APPLY_FAILED` does not prove all requested views failed. Read back the view list and verify which views actually landed.
176
+ - For views, “view exists” is not the same as “filters are active”. If `app_views_apply` returns `partial_success`, `views_verified=false`, or `details.filter_mismatches`, report the view as created but the filters as unverified until readback confirms them.
177
+ - If multiple views share the same name, do not guess which one to update. Read `view_key` from `app_read_views_summary` and pass it explicitly in `upsert_views[]`.
170
178
  - In final user-facing summaries, distinguish clearly between:
171
179
  - contract is visible / canonical shape is known
172
180
  - plan succeeded
@@ -49,10 +49,14 @@
49
49
  - Do not repeat create steps after `app_key` already exists
50
50
  - For backend rejects, keep the retry narrow: retry only the failed tool, not the whole chain
51
51
  - For `VALIDATION_ERROR`, do not keep guessing. Reuse `suggested_next_call`, `canonical_arguments`, `allowed_keys`, and `allowed_values` first.
52
+ - For layout `VALIDATION_ERROR`, also inspect `section_allowed_keys`, `section_aliases`, and `minimal_section_example`. If the same layout-shape error repeats twice, stop free-form retries and re-read `builder_tool_contract(app_layout_plan)`.
52
53
  - For flow work, do not replay internal keys from old logs or plan outputs. Public builder calls should stay on:
53
54
  - `assignees.role_ids` / `assignees.member_uids` / `assignees.member_emails`
54
55
  - `permissions.editable_fields`
55
56
  - For view work, treat `columns` as the only canonical public key. `app_read_views_summary` and `app_views_plan/apply` should all be read and written in that shape.
57
+ - For layout work, treat `title + rows` as the only canonical public section shape. `fields`, `field_ids`, and `columns` may appear in legacy/internal shapes, but they are not the preferred public write shape.
58
+ - A created view is not enough to claim a filter succeeded. When `app_views_apply` returns `partial_success`, `views_verified=false`, or `details.filter_mismatches`, treat the view as present but the filter as unverified.
59
+ - If duplicate view names exist, do not retry by name. Read the exact `view_key` and target that one.
56
60
  - If a view or flow write fails, report the smallest next action:
57
61
  - wrong key -> switch to canonical key
58
62
  - unsupported preset -> switch to allowed canonical preset
@@ -73,7 +73,7 @@ These execute normalized patches and publish by default unless `publish=false`.
73
73
  - Update fields on an existing app:
74
74
  `app_resolve -> app_read_fields -> app_schema_plan -> app_schema_apply`
75
75
  - Tidy layout:
76
- `app_read_layout_summary -> app_layout_plan -> app_layout_apply`
76
+ `app_read_fields -> app_read_layout_summary -> builder_tool_contract (if shape is unclear) -> app_layout_plan -> app_layout_apply`
77
77
  - Add workflow:
78
78
  `builder_tool_contract -> app_read_fields -> app_read_flow_summary -> role_search/member_search -> app_flow_plan -> app_flow_apply -> app_read_flow_summary`
79
79
  - Add views:
@@ -88,6 +88,7 @@ These execute normalized patches and publish by default unless `publish=false`.
88
88
  - Do not compress multiple business objects into one app with several text fields
89
89
  - Do not skip summary reads before flow or view work
90
90
  - Do not emit `column_names`; always use `columns`
91
+ - Do not model layout shape with `fields`, `field_ids`, or top-level `columns`; custom layout sections should be `title + rows`
91
92
  - Do not reuse internal flow keys such as `role_entries` or `editable_que_ids` in public builder calls
92
93
  - Do not pass natural-language preset guesses such as `default_approval`; map them to canonical preset values first
93
94
  - Do not omit assignees on approval/fill/copy nodes
@@ -6,12 +6,13 @@ Use this when fields already exist and the task is only about form grouping or o
6
6
 
7
7
  1. `app_read_fields`
8
8
  2. `app_read_layout_summary`
9
- 3. `app_layout_plan`
10
- 4. `app_layout_apply`
9
+ 3. `builder_tool_contract` when the section shape is not already obvious
10
+ 4. `app_layout_plan`
11
+ 5. `app_layout_apply`
11
12
 
12
13
  ## Example
13
14
 
14
- Plan a balanced merge layout:
15
+ Plan a custom layout with the canonical public section shape:
15
16
 
16
17
  ```json
17
18
  {
@@ -74,8 +75,17 @@ Only happens in `mode=replace`. Switch to `mode=merge` unless you intend to plac
74
75
 
75
76
  Re-read with `app_read_layout_summary`. Check `current_field_names`, `request_id`, and `suggested_next_call`.
76
77
 
78
+ ### `VALIDATION_ERROR`
79
+
80
+ Do not keep guessing section keys. Reuse `suggested_next_call`, `canonical_arguments`, `section_allowed_keys`, and `minimal_section_example`.
81
+
82
+ If the same shape error repeats twice, stop free-form retries and re-read `builder_tool_contract(app_layout_plan)`.
83
+
77
84
  ## Notes
78
85
 
79
86
  - `section_id` is optional; it is generated from the section title
80
87
  - `merge` is safer than `replace`
81
88
  - Unmentioned fields stay in place or get auto-added to `未分组字段`
89
+ - Public builder layout sections use `title + rows`
90
+ - Do not treat “一行四个字段 / 四列布局 / 每行放四个” as a top-level `columns` parameter; translate it into a `rows` matrix
91
+ - Do not prefer `fields` or `field_ids` once `rows` is known, even though MCP may normalize those shorthands for recovery
@@ -13,6 +13,12 @@ Use this when the task is only about table, card, board, or gantt views.
13
13
 
14
14
  If you are unsure about keys or view types, call `builder_tool_contract(tool_name="app_views_apply")` before guessing.
15
15
 
16
+ Important verification rule:
17
+
18
+ - `app_views_apply` can create a view object before every filter is fully verified in readback
19
+ - Do not report “筛选已成功应用” unless the apply result also shows `verification.views_verified=true`
20
+ - If apply returns `partial_success`, inspect `verification.by_view` and `details.filter_mismatches` before claiming the filters are active
21
+
16
22
  ## Example
17
23
 
18
24
  Canonical rules before any example:
@@ -22,6 +28,7 @@ Canonical rules before any example:
22
28
  - Treat `fields` only as a legacy alias the MCP may normalize, not as the preferred shape
23
29
  - Use `filters` with canonical keys `field_name`, `operator`, `value`/`values`
24
30
  - For gantt, use `start_field`, `end_field`, and optionally `title_field`
31
+ - If `app_read_views_summary` shows duplicate view names, include `view_key` in `upsert_views[]` and update that exact target
25
32
 
26
33
  Plan a default table view:
27
34
 
@@ -34,6 +41,7 @@ Plan a default table view:
34
41
  "upsert_views": [
35
42
  {
36
43
  "name": "全部订单",
44
+ "view_key": "VIEW_KEY_IF_DUPLICATE_NAMES_EXIST",
37
45
  "type": "table",
38
46
  "columns": ["订单编号", "客户名称", "订单金额", "状态"]
39
47
  }
@@ -154,10 +162,23 @@ Do not repeat `app_views_apply` with guessed keys. First:
154
162
  4. if needed, call `builder_tool_contract`
155
163
  5. retry only the minimal failed view patch
156
164
 
165
+ ### `VIEW_FILTER_READBACK_MISMATCH`
166
+
167
+ The view object was created or updated, but the readback config did not keep the intended filter values.
168
+
169
+ Treat this as:
170
+
171
+ - the view exists
172
+ - the filter is **not yet verified**
173
+
174
+ Do not tell the user the filter is active until the readback verification matches the intended filter.
175
+
157
176
  ## Notes
158
177
 
159
178
  - `fields` is accepted as an alias for `columns`, but skill examples should still use `columns`
160
179
  - `column_names` should not appear in skill examples
161
180
  - `app_read_views_summary` should be treated as canonical readback and now returns `columns`
181
+ - If `app_views_apply` returns `AMBIGUOUS_VIEW`, stop and re-run `app_read_views_summary`; then retry with the exact `view_key`
162
182
  - `filters` are ANDed together as one flat condition group
163
183
  - `app_views_apply` publishes by default
184
+ - For select-style filters, success means the backend preserved the option value in readback, not just that the view name now exists
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b14"
5
+ __version__ = "0.2.0b16"
@@ -318,15 +318,65 @@ class FieldRemovePatch(StrictModel):
318
318
  return self
319
319
 
320
320
 
321
+ def _coerce_layout_columns(value: Any) -> int | None:
322
+ if isinstance(value, bool):
323
+ return None
324
+ if isinstance(value, int):
325
+ return value if value > 0 else None
326
+ if isinstance(value, str):
327
+ stripped = value.strip()
328
+ if stripped.isdigit():
329
+ parsed = int(stripped)
330
+ return parsed if parsed > 0 else None
331
+ return None
332
+
333
+
334
+ def _normalize_layout_rows(value: Any, *, columns: int | None = None) -> Any:
335
+ if not isinstance(value, list):
336
+ return value
337
+ if value and all(isinstance(item, list) for item in value):
338
+ return value
339
+ if not value:
340
+ return []
341
+ width = columns if columns and columns > 0 else None
342
+ if width is None:
343
+ return [list(value)]
344
+ return [list(value[index : index + width]) for index in range(0, len(value), width) if value[index : index + width]]
345
+
346
+
321
347
  class LayoutSectionPatch(StrictModel):
322
348
  section_id: str | None = Field(default=None, validation_alias=AliasChoices("section_id", "sectionId"))
323
349
  title: str
324
- rows: list[list[str]] = Field(default_factory=list)
350
+ rows: list[list[Any]] = Field(default_factory=list)
351
+
352
+ @model_validator(mode="before")
353
+ @classmethod
354
+ def normalize_aliases(cls, value: Any) -> Any:
355
+ if not isinstance(value, dict):
356
+ return value
357
+ payload = dict(value)
358
+ if "name" in payload and "title" not in payload:
359
+ payload["title"] = payload.pop("name")
360
+ shorthand: Any | None = None
361
+ if "rows" not in payload:
362
+ if "fields" in payload:
363
+ shorthand = payload.pop("fields")
364
+ elif "field_ids" in payload:
365
+ shorthand = payload.pop("field_ids")
366
+ if shorthand is not None:
367
+ payload["rows"] = _normalize_layout_rows(
368
+ shorthand,
369
+ columns=_coerce_layout_columns(payload.pop("columns", None)),
370
+ )
371
+ return payload
325
372
 
326
373
  @model_validator(mode="after")
327
374
  def validate_rows(self) -> "LayoutSectionPatch":
328
375
  if not self.rows:
329
376
  raise ValueError("section rows must be a non-empty list")
377
+ for row in self.rows:
378
+ if not isinstance(row, list) or not row:
379
+ raise ValueError("section rows must be a non-empty list")
330
380
  if not self.section_id:
331
381
  self.section_id = _slugify_title(self.title)
332
382
  return self
@@ -392,6 +442,7 @@ class FlowTransitionPatch(StrictModel):
392
442
 
393
443
  class ViewUpsertPatch(StrictModel):
394
444
  name: str
445
+ view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey"))
395
446
  type: PublicViewType
396
447
  columns: list[str] = Field(default_factory=list)
397
448
  group_by: str | None = None