@josephyan/qingflow-app-builder-mcp 0.2.0-beta.12 → 0.2.0-beta.13

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.12
6
+ npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.13
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.12 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.13 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": "0.2.0-beta.12",
3
+ "version": "0.2.0-beta.13",
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.0b12"
7
+ version = "0.2.0b13"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -17,6 +17,29 @@ Before any write or verification flow, identify whether the task targets `test`
17
17
 
18
18
  Pick the smallest tool layer that can finish the task.
19
19
 
20
+ ## Mental Model
21
+
22
+ Model builder requests in four layers. Do not flatten them.
23
+
24
+ - `package`: the solution container or app bundle, for example “研发项目管理” or “费控管理系统”
25
+ - `app`: one form/app inside that package, for example “项目”, “需求”, “任务”, “缺陷”, “团队”
26
+ - `field`: one field inside one app
27
+ - `relation`: a field that links one app to another app
28
+
29
+ Interpret user intent with this hierarchy:
30
+
31
+ - If the user says “应用包”, “系统”, “包含多个表单”, “多个模块”, or asks for several named forms that relate to each other, treat it as a `package` with multiple `apps`
32
+ - Do not compress a multi-app system into one app with several text fields
33
+ - Names like “项目/需求/任务/缺陷/团队” or “费用申请/预算管理/报销审批” are usually separate apps, not text fields
34
+ - Build the apps first, then add `relation` fields to connect them
35
+
36
+ Default modeling rules:
37
+
38
+ - One business object -> one app
39
+ - Attributes of that object -> fields inside that app
40
+ - Another business object -> a separate app, not a text field
41
+ - Cross-object links -> relation fields, not text fields
42
+
20
43
  - Authentication and workspace: `auth_*`, `workspace_*`
21
44
  - File upload: `file_upload_local`
22
45
  - Resource resolve/read: `package_list`, `package_resolve`, `package_create`, `builder_tool_contract`, `member_search`, `role_search`, `app_resolve`, `app_read_summary`, `app_read_fields`, `app_read_layout_summary`, `app_read_views_summary`, `app_read_flow_summary`
@@ -45,6 +68,7 @@ Default policy:
45
68
 
46
69
  - Creating or updating one app inside an existing package: resolve the package/app, read compact state, plan patches on the server, then apply schema/layout/flow/views patches explicitly.
47
70
  - If package creation looks necessary or beneficial, ask the user to confirm before calling `package_create`.
71
+ - If the user describes a system/package with multiple forms or modules, do not start with `app_schema_apply` on the package name. Resolve or create the package first, then create each app separately.
48
72
 
49
73
  ## Standard Operating Order
50
74
 
@@ -57,16 +81,19 @@ Before any business tool:
57
81
  For builder work:
58
82
 
59
83
  1. Resolve the target package with `package_resolve`; if resolution is ambiguous or you need a read-only fallback, use `package_list`. If you believe a new package should be created, ask the user to confirm before calling `package_create`.
60
- 2. Resolve the target app with `app_resolve` if the request is an update.
61
- 3. Read only the smallest summary you need: `app_read_summary`, `app_read_fields`, `app_read_layout_summary`, `app_read_views_summary`, `app_read_flow_summary`.
62
- 4. Use `app_schema_plan`, `app_layout_plan`, `app_flow_plan`, or `app_views_plan` before any non-trivial write.
63
- 5. Use `app_schema_apply` for create/upsert/remove field work. It publishes by default after the patch lands.
64
- 6. If the app must belong to a package, use `package_attach_app` explicitly after schema work unless readback already shows the target `tag_id`.
65
- 7. Use `app_layout_apply` only when the user is explicitly changing layout. Prefer the default `mode=merge`; use `mode=replace` only when you intend to place every field explicitly. It publishes by default.
66
- 8. Use `app_flow_apply` after schema exists. It publishes by default.
67
- 9. Use `app_views_apply` when the user wants explicit table/card/board/gantt views. It publishes by default.
68
- 10. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need an explicit verification pass.
69
- 11. If a write fails with `APP_EDIT_LOCKED`, stop normal writes. Only use `app_release_edit_lock_if_mine` when the failed result shows the lock owner is the current logged-in user.
84
+ 2. Decide whether the target is one app or a multi-app package:
85
+ - one app: continue with `app_resolve`
86
+ - multi-app package/system: create or resolve the package, then create each app separately before adding relations
87
+ 3. Resolve the target app with `app_resolve` if the request is an update.
88
+ 4. Read only the smallest summary you need: `app_read_summary`, `app_read_fields`, `app_read_layout_summary`, `app_read_views_summary`, `app_read_flow_summary`.
89
+ 5. Use `app_schema_plan`, `app_layout_plan`, `app_flow_plan`, or `app_views_plan` before any non-trivial write.
90
+ 6. Use `app_schema_apply` for create/upsert/remove field work. It publishes by default after the patch lands.
91
+ 7. If the app must belong to a package, use `package_attach_app` explicitly after schema work unless readback already shows the target `tag_id`.
92
+ 8. Use `app_layout_apply` only when the user is explicitly changing layout. Prefer the default `mode=merge`; use `mode=replace` only when you intend to place every field explicitly. It publishes by default.
93
+ 9. Use `app_flow_apply` after schema exists. It publishes by default.
94
+ 10. Use `app_views_apply` when the user wants explicit table/card/board/gantt views. It publishes by default.
95
+ 11. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need an explicit verification pass.
96
+ 12. If a write fails with `APP_EDIT_LOCKED`, stop normal writes. Only use `app_release_edit_lock_if_mine` when the failed result shows the lock owner is the current logged-in user.
70
97
 
71
98
  For view work, keep the order strict:
72
99
 
@@ -86,13 +113,17 @@ For flow work, keep the order strict:
86
113
  5. `role_create` if the user wants a reusable role and no suitable role exists yet
87
114
  6. Start from a canonical preset when possible
88
115
  7. Use patch-style edits to that skeleton instead of freehand full-graph generation
89
- 8. Declare approver/fill/copy assignees explicitly:
116
+ 8. When patching a preset skeleton, reuse the preset node ids:
117
+ - `basic_approval` -> patch `approve_1`
118
+ - `basic_fill_then_approve` -> patch `fill_1` and `approve_1`
119
+ Do not invent a second approval/fill node id unless you are intentionally replacing the skeleton and removing the preset node.
120
+ 9. Declare approver/fill/copy assignees explicitly:
90
121
  - prefer `assignees.role_names`
91
122
  - support `assignees.member_names` / `assignees.member_emails` / `assignees.member_uids`
92
- 9. When a node must edit specific fields, declare `permissions.editable_fields`
93
- 10. `app_flow_plan`
94
- 11. `app_flow_apply`
95
- 12. `app_read_flow_summary` after apply whenever the user asked for verification or apply returns `partial_success`
123
+ 10. When a node must edit specific fields, declare `permissions.editable_fields`
124
+ 11. `app_flow_plan`
125
+ 12. `app_flow_apply`
126
+ 13. `app_read_flow_summary` after apply whenever the user asked for verification or apply returns `partial_success`
96
127
 
97
128
  In `prod`, keep `plan` and `apply` as separate phases unless the user explicitly asks for a direct live execution.
98
129
 
@@ -109,9 +140,11 @@ For additive work on existing systems:
109
140
  - `app_schema_apply` is the only public patch tool allowed to create an app shell; `app_layout_apply`, `app_flow_apply`, and `app_views_apply` require an existing app.
110
141
  - Prefer `*_plan` before `*_apply`; the plan tools do server-side normalization and return the next executable call skeleton.
111
142
  - For abstract requests like “默认视图”, “基础审批流”, “灵活流程”, or “美观布局”, first translate the intent into a stable preset or explicit patch. Do not send those phrases to MCP unchanged.
143
+ - If the user asks for a business system or package that contains several forms, do not use the system/package name as `app_name` and do not try to store the child app names as text fields.
112
144
  - For flexible workflow requests, split the work into two steps:
113
145
  1. create a base skeleton with a preset
114
146
  2. apply explicit business-specific changes as patchable nodes/transitions
147
+ - For preset-based flows, treat preset node ids as part of the public contract. Patch the skeleton nodes by the same ids instead of creating a parallel node with a new id and leaving the preset node unassigned.
115
148
  - Approval, fill, and copy nodes must declare at least one assignee. Treat this as a hard requirement, not an optional detail.
116
149
  - For workflow nodes, use the canonical public shape:
117
150
  - `assignees.role_names`
@@ -123,6 +156,7 @@ For additive work on existing systems:
123
156
  - `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.
124
157
  - `package_attach_app` is the source of truth for package ownership; do not assume app creation or publish implicitly attaches the app.
125
158
  - `relation` and `subtable` must be explicit; do not infer them from vague natural language.
159
+ - Another app is not a field. If two business objects should both have their own records, build two apps and connect them with relation fields.
126
160
  - In `prod`, prefer explicit patch tools and avoid any speculative create flow.
127
161
  - Never try to bypass collaborative edit locks. `app_release_edit_lock_if_mine` is only for the case where the lock owner is the current authenticated user.
128
162
 
@@ -2,6 +2,18 @@
2
2
 
3
3
  Use this when the user wants one new app inside an existing package.
4
4
 
5
+ Do not use this playbook when the user is really asking for a system/package with multiple forms or modules. In that case:
6
+
7
+ 1. resolve or create the package
8
+ 2. create each app separately
9
+ 3. attach each app to the package
10
+ 4. add relation fields between apps
11
+
12
+ Hierarchy reminder:
13
+
14
+ - package -> app -> field -> relation
15
+ - another business object is another app, not a text field
16
+
5
17
  If creating a brand new package would help, ask the user to confirm package creation first. After confirmation, call `package_create` before this sequence.
6
18
 
7
19
  ## Minimal sequence
@@ -13,6 +25,22 @@ If creating a brand new package would help, ask the user to confirm package crea
13
25
  5. `package_attach_app`
14
26
  6. `app_publish_verify` only when the user asks for explicit final verification
15
27
 
28
+ ## Multi-app systems are different
29
+
30
+ If the user says things like:
31
+
32
+ - “创建一个完整应用包”
33
+ - “包含项目、需求、任务、缺陷、团队这几个表单”
34
+ - “这些表单之间建立关联关系”
35
+
36
+ then do not treat that as one app.
37
+
38
+ Use this pattern instead:
39
+
40
+ 1. `package_resolve` or `package_create`
41
+ 2. for each app name, run `app_schema_plan -> app_schema_apply -> package_attach_app`
42
+ 3. once the apps exist, add `relation` fields between them
43
+
16
44
  ## Example
17
45
 
18
46
  Create a new package only after the user confirms package creation:
@@ -110,3 +138,11 @@ The create route did not resolve in the current backend route context. Re-run `w
110
138
  ### `PACKAGE_ATTACH_FAILED`
111
139
 
112
140
  Do not retry schema creation. Re-run only `package_attach_app`, then verify with `app_read_summary`.
141
+
142
+ ### Hierarchy modeling mistake
143
+
144
+ If the user asked for several named forms/apps but the draft patch turns them into text fields inside one app, stop and remodel the task as:
145
+
146
+ - one package
147
+ - many apps
148
+ - relation fields between those apps
@@ -11,6 +11,13 @@
11
11
  - Treat `package_attach_app` as the source of truth for package ownership
12
12
  - Check `tag_ids_after` after schema writes
13
13
 
14
+ ## Package vs app vs field
15
+
16
+ - A package/system is not an app
17
+ - Another app is not a field
18
+ - If the user names multiple forms/modules that relate to each other, create multiple apps and connect them with relation fields
19
+ - Do not use child app names like “项目/需求/任务/缺陷/团队” as plain text fields inside one app
20
+
14
21
  ## Auto publish
15
22
 
16
23
  - `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, and `app_views_apply` publish by default
@@ -32,6 +39,7 @@
32
39
  - Prefer roles over explicit members unless the user explicitly names people
33
40
  - Resolve assignees with `role_search` / `member_search` before flow apply
34
41
  - Use `permissions.editable_fields` for node-level editable field permissions; do not guess field ids
42
+ - Preset node ids matter. When patching `basic_approval` or `basic_fill_then_approve`, reuse `approve_1` and `fill_1` unless you are explicitly replacing the skeleton.
35
43
  - If `app_flow_plan` reports `FLOW_DEPENDENCY_MISSING`, fix schema first
36
44
  - Do not switch to hidden `solution_*` tools from public builder flows
37
45
 
@@ -8,6 +8,17 @@ Use the smallest v2 builder tool chain that can finish the task.
8
8
 
9
9
  Use `plan` before `apply` unless the patch is trivial and already normalized.
10
10
 
11
+ ## Hierarchy first
12
+
13
+ Before picking tools, decide which layer the request targets:
14
+
15
+ - `package`: a solution/app bundle like “研发项目管理” or “费控管理系统”
16
+ - `app`: one form/app inside that package
17
+ - `field`: one field inside one app
18
+ - `relation`: a field that links two apps
19
+
20
+ If the user asks for multiple forms/modules that relate to each other, this is a package-level multi-app task, not a single-app create.
21
+
11
22
  ## Resolve
12
23
 
13
24
  - `package_create`: create a new package only after the user confirms package creation; exact-name duplicates return `noop=true`
@@ -57,6 +68,8 @@ These execute normalized patches and publish by default unless `publish=false`.
57
68
  `package_resolve -> app_resolve -> app_schema_plan -> app_schema_apply -> package_attach_app`
58
69
  - Create a brand new package, then create one app in it:
59
70
  `package_create -> package_resolve -> app_schema_plan -> app_schema_apply -> package_attach_app`
71
+ - Create a brand new multi-app system/package:
72
+ `package_create/resolve -> per-app app_schema_plan/apply -> package_attach_app per app -> relation field patches`
60
73
  - Update fields on an existing app:
61
74
  `app_resolve -> app_read_fields -> app_schema_plan -> app_schema_apply`
62
75
  - Tidy layout:
@@ -71,9 +84,12 @@ These execute normalized patches and publish by default unless `publish=false`.
71
84
  - Do not handcraft raw Qingflow schema payloads
72
85
  - Do not rely on internal `solution_*` tools in public builder flows
73
86
  - Do not create a new package without first asking the user to confirm package creation
87
+ - Do not treat a package/system name as `app_name` when the user clearly wants multiple apps inside it
88
+ - Do not compress multiple business objects into one app with several text fields
74
89
  - Do not skip summary reads before flow or view work
75
90
  - Do not emit `column_names`; always use `columns`
76
91
  - Do not reuse internal flow keys such as `role_entries` or `editable_que_ids` in public builder calls
77
92
  - Do not pass natural-language preset guesses such as `default_approval`; map them to canonical preset values first
78
93
  - Do not omit assignees on approval/fill/copy nodes
94
+ - Do not patch preset flows with brand new approval/fill node ids unless you are intentionally replacing the skeleton; reuse preset ids like `approve_1` and `fill_1`
79
95
  - Do not guess role ids, member ids, or editable field ids; resolve names first
@@ -11,9 +11,14 @@ Use this when the app already exists and the task is only about workflow.
11
11
  5. `role_create` if the user wants a reusable directory role and no good role exists
12
12
  6. start from a canonical preset when possible
13
13
  7. patch the skeleton instead of freehanding a full graph
14
- 8. `app_flow_plan`
15
- 9. `app_flow_apply`
16
- 10. `app_read_flow_summary` when apply returns `partial_success` or the user asked for verification
14
+ 8. reuse preset node ids when patching:
15
+ - `basic_approval` -> patch `approve_1`
16
+ - `basic_fill_then_approve` -> patch `fill_1` and `approve_1`
17
+ Do not add a second approval/fill node with a new id unless you are intentionally replacing the skeleton.
18
+ The MCP now auto-aligns the simplest single-node preset overrides, but still prefer explicit preset ids so the merged graph stays predictable.
19
+ 9. `app_flow_plan`
20
+ 10. `app_flow_apply`
21
+ 11. `app_read_flow_summary` when apply returns `partial_success` or the user asked for verification
17
22
 
18
23
  If you are unsure about presets or node shapes, call `builder_tool_contract(tool_name="app_flow_apply")` before guessing.
19
24
 
@@ -145,6 +150,7 @@ Only after that should you use explicit nodes:
145
150
  ```
146
151
 
147
152
  After `app_flow_plan` succeeds, prefer reusing its `suggested_next_call.arguments` directly. Do not rewrite the result into internal fields such as `role_entries` or `editable_que_ids`.
153
+ When you patch a preset, patch the preset node itself. Do not leave the preset approval node unassigned while adding a second custom approval node.
148
154
 
149
155
  ## Common failures
150
156
 
@@ -152,12 +158,19 @@ After `app_flow_plan` succeeds, prefer reusing its `suggested_next_call.argument
152
158
 
153
159
  Approval, fill, and copy nodes must declare at least one assignee.
154
160
 
161
+ If this happens after using a preset, check for this specific mistake first:
162
+
163
+ - the preset created `approve_1` or `fill_1`
164
+ - your patch created a different node id instead of patching that preset node
165
+ - the original preset node remained in the graph without assignees
166
+
155
167
  Preferred fix order:
156
168
 
157
169
  1. `role_search`
158
170
  2. `member_search` only if the user explicitly named members
159
171
  3. `role_create` if the business needs a reusable role
160
- 4. retry `app_flow_plan` or `app_flow_apply` with canonical `assignees.*`
172
+ 4. patch the preset node id itself with canonical `assignees.*`
173
+ 5. retry `app_flow_plan` or `app_flow_apply`
161
174
 
162
175
  ### `FLOW_DEPENDENCY_MISSING`
163
176
 
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b12"
5
+ __version__ = "0.2.0b13"
@@ -3624,6 +3624,11 @@ def _merge_flow_graph(
3624
3624
  ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
3625
3625
  if not override_nodes and not override_transitions:
3626
3626
  return deepcopy(base_nodes), deepcopy(base_transitions)
3627
+ override_nodes, override_transitions = _align_flow_preset_override_ids(
3628
+ base_nodes=base_nodes,
3629
+ override_nodes=override_nodes,
3630
+ override_transitions=override_transitions,
3631
+ )
3627
3632
  merged_nodes: list[dict[str, Any]] = []
3628
3633
  override_map = {
3629
3634
  str(node.get("id") or ""): deepcopy(node)
@@ -3654,6 +3659,72 @@ def _merge_flow_graph(
3654
3659
  return merged_nodes, merged_transitions
3655
3660
 
3656
3661
 
3662
+ def _align_flow_preset_override_ids(
3663
+ *,
3664
+ base_nodes: list[dict[str, Any]],
3665
+ override_nodes: list[dict[str, Any]],
3666
+ override_transitions: list[dict[str, Any]],
3667
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
3668
+ """Map simple preset skeleton overrides back onto canonical preset node ids.
3669
+
3670
+ This keeps preset-based plans stable when the caller customizes the single
3671
+ approval/fill/copy step semantically but forgets to reuse ids such as
3672
+ `approve_1` or `fill_1`.
3673
+ """
3674
+ patchable_types = {"approve", "fill", "copy"}
3675
+ base_by_type: dict[str, list[str]] = {}
3676
+ override_by_type: dict[str, list[str]] = {}
3677
+ for node in base_nodes:
3678
+ if not isinstance(node, dict):
3679
+ continue
3680
+ node_type = str(node.get("type") or "")
3681
+ node_id = str(node.get("id") or "")
3682
+ if node_type in patchable_types and node_id:
3683
+ base_by_type.setdefault(node_type, []).append(node_id)
3684
+ for node in override_nodes:
3685
+ if not isinstance(node, dict):
3686
+ continue
3687
+ node_type = str(node.get("type") or "")
3688
+ node_id = str(node.get("id") or "")
3689
+ if node_type in patchable_types and node_id:
3690
+ override_by_type.setdefault(node_type, []).append(node_id)
3691
+ replacement_map: dict[str, str] = {}
3692
+ for node_type in patchable_types:
3693
+ base_ids = base_by_type.get(node_type) or []
3694
+ override_ids = override_by_type.get(node_type) or []
3695
+ if len(base_ids) != 1 or len(override_ids) != 1:
3696
+ continue
3697
+ base_id = base_ids[0]
3698
+ override_id = override_ids[0]
3699
+ if override_id == base_id:
3700
+ continue
3701
+ replacement_map[override_id] = base_id
3702
+ if not replacement_map:
3703
+ return deepcopy(override_nodes), deepcopy(override_transitions)
3704
+ aligned_nodes: list[dict[str, Any]] = []
3705
+ for node in override_nodes:
3706
+ if not isinstance(node, dict):
3707
+ continue
3708
+ aligned = deepcopy(node)
3709
+ node_id = str(aligned.get("id") or "")
3710
+ if node_id in replacement_map:
3711
+ aligned["id"] = replacement_map[node_id]
3712
+ aligned_nodes.append(aligned)
3713
+ aligned_transitions: list[dict[str, Any]] = []
3714
+ for transition in override_transitions:
3715
+ if not isinstance(transition, dict):
3716
+ continue
3717
+ aligned = deepcopy(transition)
3718
+ source = str(aligned.get("from") or "")
3719
+ target = str(aligned.get("to") or "")
3720
+ if source in replacement_map:
3721
+ aligned["from"] = replacement_map[source]
3722
+ if target in replacement_map:
3723
+ aligned["to"] = replacement_map[target]
3724
+ aligned_transitions.append(aligned)
3725
+ return aligned_nodes, aligned_transitions
3726
+
3727
+
3657
3728
  def _extract_directory_items(listed: JSONObject) -> list[dict[str, Any]]:
3658
3729
  if isinstance(listed.get("items"), list):
3659
3730
  return [item for item in listed["items"] if isinstance(item, dict)]