@qingflow-tech/qingflow-app-user-mcp 1.0.10 → 1.0.12

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.
Files changed (89) hide show
  1. package/README.md +9 -3
  2. package/docs/local-agent-install.md +54 -3
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-skills.mjs +5 -0
  5. package/npm/lib/runtime.mjs +304 -13
  6. package/npm/scripts/postinstall.mjs +1 -5
  7. package/package.json +3 -2
  8. package/pyproject.toml +1 -1
  9. package/skills/qingflow-app-builder/SKILL.md +255 -0
  10. package/skills/qingflow-app-builder/agents/openai.yaml +4 -0
  11. package/skills/qingflow-app-builder/references/create-app.md +149 -0
  12. package/skills/qingflow-app-builder/references/environments.md +63 -0
  13. package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
  14. package/skills/qingflow-app-builder/references/gotchas.md +107 -0
  15. package/skills/qingflow-app-builder/references/match-rules.md +114 -0
  16. package/skills/qingflow-app-builder/references/public-surface-sync.md +75 -0
  17. package/skills/qingflow-app-builder/references/solution-playbooks.md +52 -0
  18. package/skills/qingflow-app-builder/references/tool-selection.md +99 -0
  19. package/skills/qingflow-app-builder/references/update-flow.md +158 -0
  20. package/skills/qingflow-app-builder/references/update-layout.md +68 -0
  21. package/skills/qingflow-app-builder/references/update-schema.md +72 -0
  22. package/skills/qingflow-app-builder/references/update-views.md +284 -0
  23. package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
  24. package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
  25. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
  26. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
  27. package/skills/qingflow-app-user/SKILL.md +12 -11
  28. package/skills/qingflow-app-user/references/data-gotchas.md +2 -2
  29. package/skills/qingflow-app-user/references/public-surface-sync.md +3 -3
  30. package/skills/qingflow-app-user/references/record-patterns.md +5 -5
  31. package/skills/qingflow-app-user/references/workflow-usage.md +4 -5
  32. package/skills/qingflow-mcp-setup/SKILL.md +113 -0
  33. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  34. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  35. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  36. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  37. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  38. package/skills/qingflow-record-analysis/SKILL.md +6 -7
  39. package/skills/qingflow-record-analysis/manifest.yaml +10 -0
  40. package/skills/qingflow-record-delete/SKILL.md +5 -3
  41. package/skills/qingflow-record-import/SKILL.md +6 -2
  42. package/skills/qingflow-record-insert/SKILL.md +48 -4
  43. package/skills/qingflow-record-insert/manifest.yaml +6 -0
  44. package/skills/qingflow-record-update/SKILL.md +36 -24
  45. package/skills/qingflow-task-ops/SKILL.md +25 -25
  46. package/skills/qingflow-task-ops/references/environments.md +0 -1
  47. package/skills/qingflow-task-ops/references/workflow-usage.md +4 -6
  48. package/src/qingflow_mcp/__main__.py +6 -2
  49. package/src/qingflow_mcp/builder_facade/models.py +41 -2
  50. package/src/qingflow_mcp/builder_facade/service.py +2743 -423
  51. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  52. package/src/qingflow_mcp/cli/commands/builder.py +30 -4
  53. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  54. package/src/qingflow_mcp/cli/commands/imports.py +1 -1
  55. package/src/qingflow_mcp/cli/commands/record.py +54 -11
  56. package/src/qingflow_mcp/cli/context.py +0 -3
  57. package/src/qingflow_mcp/cli/formatters.py +238 -8
  58. package/src/qingflow_mcp/cli/main.py +47 -3
  59. package/src/qingflow_mcp/errors.py +43 -2
  60. package/src/qingflow_mcp/public_surface.py +24 -16
  61. package/src/qingflow_mcp/response_trim.py +119 -12
  62. package/src/qingflow_mcp/server.py +17 -14
  63. package/src/qingflow_mcp/server_app_builder.py +29 -7
  64. package/src/qingflow_mcp/server_app_user.py +23 -24
  65. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  66. package/src/qingflow_mcp/solution/executor.py +112 -15
  67. package/src/qingflow_mcp/tools/ai_builder_tools.py +497 -65
  68. package/src/qingflow_mcp/tools/app_tools.py +237 -51
  69. package/src/qingflow_mcp/tools/approval_tools.py +196 -34
  70. package/src/qingflow_mcp/tools/auth_tools.py +92 -16
  71. package/src/qingflow_mcp/tools/code_block_tools.py +296 -39
  72. package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
  73. package/src/qingflow_mcp/tools/directory_tools.py +236 -72
  74. package/src/qingflow_mcp/tools/export_tools.py +230 -33
  75. package/src/qingflow_mcp/tools/file_tools.py +7 -3
  76. package/src/qingflow_mcp/tools/import_tools.py +293 -40
  77. package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
  78. package/src/qingflow_mcp/tools/package_tools.py +134 -8
  79. package/src/qingflow_mcp/tools/portal_tools.py +39 -3
  80. package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
  81. package/src/qingflow_mcp/tools/record_tools.py +2305 -442
  82. package/src/qingflow_mcp/tools/resource_read_tools.py +191 -39
  83. package/src/qingflow_mcp/tools/role_tools.py +80 -9
  84. package/src/qingflow_mcp/tools/solution_tools.py +57 -15
  85. package/src/qingflow_mcp/tools/task_context_tools.py +569 -119
  86. package/src/qingflow_mcp/tools/task_tools.py +113 -29
  87. package/src/qingflow_mcp/tools/view_tools.py +106 -3
  88. package/src/qingflow_mcp/tools/workflow_tools.py +17 -1
  89. package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  name: qingflow-record-insert
3
- description: Create Qingflow records with a schema-first insert workflow after the MCP is already connected and authenticated.
3
+ description: Create Qingflow records with a schema-first insert workflow.
4
4
  metadata:
5
5
  short-description: Schema-first Qingflow record insert
6
+
6
7
  ---
7
8
 
8
9
  # Qingflow Record Insert
@@ -21,6 +22,43 @@ Default to batch-shaped insert. A single new record is `items` with one row.
21
22
  - `record_department_candidates`
22
23
  - `file_upload_local`
23
24
 
25
+ ## Candidate Lookup
26
+
27
+ Do not pre-query member or department ids by default. Use candidate commands only when:
28
+
29
+ - the user explicitly asks to see candidates
30
+ - insert returns `needs_confirmation`
31
+ - member / department names are likely duplicated
32
+ - a natural value is outside the field's candidate scope
33
+
34
+ ```bash
35
+ qingflow --json record member-candidates \
36
+ --app-key APP_KEY \
37
+ --field-id FIELD_ID \
38
+ --keyword "张三"
39
+ ```
40
+
41
+ ```bash
42
+ qingflow --json record department-candidates \
43
+ --app-key APP_KEY \
44
+ --field-id FIELD_ID \
45
+ --keyword "直销部"
46
+ ```
47
+
48
+ If the candidate scope must match an existing runtime context, pass the current record or pending fields:
49
+
50
+ ```bash
51
+ qingflow --json record member-candidates \
52
+ --app-key APP_KEY \
53
+ --field-id FIELD_ID \
54
+ --record-id RECORD_ID \
55
+ --workflow-node-id WORKFLOW_NODE_ID \
56
+ --fields-file pending_fields.json \
57
+ --keyword "张三"
58
+ ```
59
+
60
+ Without `record_id` / `workflow_node_id` / `fields-file`, the result is a static applicant-node preview. It is useful for explicit browsing, but not proof that every candidate is valid in a later workflow-specific write.
61
+
24
62
  ## Working Rules
25
63
 
26
64
  1. Start with `record_insert_schema_get`
@@ -32,13 +70,13 @@ Default to batch-shaped insert. A single new record is `items` with one row.
32
70
  7. For `linkage.kind=logic_visibility`, read `sources` as upstream trigger fields and treat `role=manual_input_after_activation` as "fill this only after the upstream condition is satisfied"
33
71
  8. For `linkage.kind=reference_fill`, prefer filling the source field first; treat target fields with `role=auto_fill_preferred` or `auto_fill_only` as reference-driven outputs rather than blind manual inputs
34
72
  9. For `linkage.kind=formula_fill`, treat the field as formula/default-auto-fill driven unless the user explicitly asks to override it and the field is still writable
35
- 10. If insert succeeds and single-record detail/readback matters, prefer `record_get`; use `record_list(..., output_profile="normalized")` only for batch row-shaped normalized readback
73
+ 10. If insert succeeds and single-record detail/readback matters, prefer `record_get`; for batch verification, rely on returned `created_record_ids` first, then use `record_get` for selected rows or `record_access -> Python` when a row-shaped bulk check is truly needed
36
74
  11. Keep subtable payloads under the parent field as a row array
37
75
  12. Member / department / relation fields may accept natural strings directly, such as `"张三"`, `"直销部"`, or `"海军军医大学"`; do not pre-query ids by default
38
76
  13. If the write returns `status="needs_confirmation"`, stop and surface the candidates
39
77
  14. Retry failed rows only with explicit ids / objects after the user confirms
40
78
  15. Keep `verify_write=true` for production inserts
41
- 16. If post-write detail context matters, read `record_get.fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs and do not expect legacy `data.normalized_record`
79
+ 16. If post-write detail context matters, read `record_get.fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs and do not expect legacy flat record shapes
42
80
  17. Treat nested schema shape as guidance, not a brittle contract; do not hard-code transient implementation details like optional nested `field_id` shape when composing inserts
43
81
  18. For `partial_success`, read `created_record_ids`, then repair only the failed `items[].row_number` using `failed_fields`; never retry the whole batch after any row has `write_executed=true`
44
82
 
@@ -58,10 +96,16 @@ Default to batch-shaped insert. A single new record is `items` with one row.
58
96
 
59
97
  ## CLI Pattern
60
98
 
99
+ CLI fallback for the schema step:
100
+
101
+ ```bash
102
+ qingflow --json record schema insert --app-key APP_KEY > tmp/qingflow_insert_schema.json
103
+ ```
104
+
61
105
  Use a JSON array file:
62
106
 
63
107
  ```bash
64
- qingflow record insert --app-key APP_KEY --items-file records.json --json
108
+ qingflow --json record insert --app-key APP_KEY --items-file records.json
65
109
  ```
66
110
 
67
111
  `records.json`:
@@ -0,0 +1,6 @@
1
+ identity: qingflow-record-insert
2
+ type: skill
3
+ description: Create Qingflow records. Default to record_insert_schema_get -> record_insert(items); one row is still an items array. Member, department, and relation fields prefer natural language, and failures are recovered from created_record_ids plus failed_fields.
4
+ tags:
5
+ - cli
6
+ - qingflow
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: qingflow-record-update
3
- description: Update Qingflow records with a schema-first record-specific workflow after the MCP is already connected and authenticated.
3
+ description: Update Qingflow records through the current record detail context using the Wingent Momo runtime MCP session; recover auth/workspace only after a tool error.
4
4
  metadata:
5
- short-description: Schema-first Qingflow record update
5
+ short-description: Detail-first Qingflow record update
6
6
  ---
7
7
 
8
8
  # Qingflow Record Update
9
9
 
10
10
  ## Default Path
11
11
 
12
- `record_update_schema_get -> record_update`
12
+ `record_get -> record_update`
13
13
 
14
14
  ## Core Tools
15
15
 
@@ -20,28 +20,40 @@ metadata:
20
20
 
21
21
  ## Working Rules
22
22
 
23
- 1. Start with `record_update_schema_get(app_key, record_id)`
24
- 2. Use only `writable_fields` and `payload_template` from that response
25
- 3. Read field-level `linkage` before composing updates; it is the static hint for whether a field is a linked source, linked target, reference-driven auto fill field, or formula-driven field
26
- 4. Build `fields` as a field-title keyed map
27
- 5. Build `fields` only from that returned field set; MCP will choose the first matched accessible view that can execute the payload
28
- 6. If a field has `linkage.affects_fields`, treat it as a source field; changing it may also change how other fields are interpreted or auto-filled
29
- 7. If a field has `linkage.sources`, treat those titles as upstream dependencies that explain why the field matters or why its validation can change
30
- 8. For `linkage.kind=reference_fill`, prefer updating the source reference field first and treat `role=auto_fill_preferred` or `auto_fill_only` targets as outputs of that reference relationship
31
- 9. For `linkage.kind=formula_fill`, treat the field as formula/default-auto-fill driven unless the field is still clearly writable and the user explicitly wants to override it
32
- 10. Keep `verify_write=true` for production updates
33
- 11. If the write returns `status="needs_confirmation"`, stop and surface the candidates
34
- 12. Do not assume any arbitrary combination of writable fields will succeed; one single matched accessible view still has to cover the payload
35
- 13. Do not look for any extra context bucket in update schema; lookup behavior stays inline on the field definitions themselves
36
- 14. When update context feels unstable, trust `record_update_schema_get`'s route-aware matched-view result over guessed `view_id` or remembered UI scope
37
- 15. If single-record detail/readback matters, prefer `record_get` after the write and read top-level `fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs; use `record_list(..., output_profile="normalized")` only for batch row-shaped normalized readback
38
- 16. For batch updates, read top-level `mode`, `dry_run`, `total`, `succeeded`, `failed`, `needs_confirmation`, `updated_record_ids`, `write_executed`, `safe_to_retry`, `verification_status`, and `items[].row_number/status/record_id`
39
- 17. If `write_executed=true`, do not blindly retry the whole batch; use `items[]` and `updated_record_ids` to decide whether only failed rows need repair
23
+ 1. Start with `record_get(app_key, record_id, view_id when known)` to read the current detail-page context; if the frontend/custom view is known, keep that same `view_id` through the update
24
+ 2. Use `record_get.fields[]` for field titles, `field_id`, `kind`, and current values
25
+ 3. Build `fields` as a field-title keyed map containing only the user-requested changes
26
+ 4. Run `record_update` directly with that key/value map; pass the same `view_id` when it is known so the tool tries that frontend view first, then let the tool fall back to other executable routes if needed
27
+ 5. Keep `verify_write=true` for production updates
28
+ 6. If the write returns `status="needs_confirmation"`, stop and surface the candidates
29
+ 7. On success, read only the final status, `update_route`, `write_executed`, and verification result; do not surface intermediate route failures as the outcome
30
+ 8. On failure, surface the failed reason and field/path diagnostics returned by `record_update`
31
+ 9. Use `record_update_schema_get` only after an update failure, field ambiguity, or explicit request to diagnose writable fields/routes
32
+ 10. Treat app-level `viewList` or applicant/insert schema 40002 as auxiliary-context loss, not as final record permission denial, when `record_get` or `record_update` succeeds through the selected `custom:*` view or `system:all`/type=8 route
33
+ 11. If single-record readback matters, prefer `record_get` after the write and read top-level `fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs; use `record_list(..., output_profile="normalized")` only for batch row-shaped normalized readback
34
+ 12. For batch updates, read top-level `mode`, `dry_run`, `total`, `succeeded`, `failed`, `needs_confirmation`, `updated_record_ids`, `write_executed`, `safe_to_retry`, `verification_status`, and `items[].row_number/status/record_id`
35
+ 13. If `write_executed=true`, do not blindly retry the whole batch; use `items[]` and `updated_record_ids` to decide whether only failed rows need repair
36
+
37
+ ## Special Field Values
38
+
39
+ Use the field `kind` from `record_get.fields[]` when shaping values:
40
+
41
+ - `member`: start with a natural-language name such as `"周颖"`. If `record_update` returns `status="needs_confirmation"`, no write happened; retry with one candidate object, for example `{"uid":1048599,"name":"沈嘉慧Seth","email":"shenjiahui@exiao.tech"}`.
42
+ - `department`: start with a department name such as `"客户成功部"`. The record tool resolves names through the member-visible directory path first; do not pre-query ContactAuth-only contact management APIs for ids. On `needs_confirmation`, retry with the explicit candidate object/id returned by the tool.
43
+ - `relation`: prefer a unique human-readable target value or `{"apply_id":"..."}`. On multiple matches, stop and retry only after choosing one `confirmation_requests[].candidates[]` item.
44
+ - `select`: single select uses one option string; multi select uses an array of option strings.
45
+ - `attachment`: use a supported uploaded/file object from the returned format hints; do not invent remote URLs when the tool asks for upload.
46
+ - Reference, formula, auto-fill, readonly, and system fields are not independent writable kinds. Do not force-write the filled target field; update the upstream driving field instead, or surface the blocker.
47
+
48
+ `needs_confirmation` means `write_executed=false`: the tool found candidates but did not have enough certainty to write. Do not report it as success or failure. Surface the candidate list if user choice is needed; if the intended candidate is obvious from the user request or prior context, retry with the explicit object and then verify the final value.
49
+
50
+ For member or department ambiguity, use the record candidate tools (`record_member_candidates` / `record_department_candidates`, or CLI `qingflow record member-candidates` / `department-candidates`). These follow the same field-scope candidate route as the frontend selector; do not replace them with contact-directory management queries.
40
51
 
41
52
  ## Do Not
42
53
 
43
- - Do not pass any `view_*` selector
44
- - Do not update fields missing from `writable_fields`
54
+ - Do not pass legacy `view_key` / `view_name` selectors; use `view_id` only when the frontend/detail view is known
55
+ - Do not call `record_update_schema_get` as the normal pre-step for every update
56
+ - Do not use applicant/insert schema to decide record update fields
57
+ - Do not update fields that were absent from `record_get.fields[]` unless the user explicitly provided a raw field id and you can justify it
45
58
  - Do not resolve lookup fields against a guessed record context
46
- - Do not ignore `linkage.affects_fields` when changing source-like fields on complex forms
47
- - Do not fall back to guessed browse scopes when `record_update_schema_get` already tells you which matched route can or cannot execute the payload
59
+ - Do not treat a denied intermediate route as final failure when `record_update` later succeeds through another route
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qingflow-task-ops
3
- description: Use Qingflow todo discovery, workflow task context, associated approval context, workflow logs, and unified task actions after the MCP is already connected and authenticated. Do not use this skill for record CRUD or final statistical analysis.
3
+ description: Use Qingflow todo discovery, workflow task context, associated approval context, workflow logs, and unified task actions with the current Wingent Momo runtime MCP session. Do not use this skill for record CRUD or final statistical analysis.
4
4
  metadata:
5
5
  short-description: Qingflow task workflow context and actions
6
6
  ---
@@ -10,23 +10,23 @@ metadata:
10
10
  ## Overview
11
11
 
12
12
  This skill is for task workflow operations only.
13
- Assumes MCP is connected, authenticated, and on the correct workspace.
13
+ In Wingent Momo runtime, trust injected MCP credentials, workspace, and route context until a business tool explicitly reports otherwise.
14
14
  Before executing, skim the shared maintenance baseline: [public-surface-sync.md](../qingflow-app-user/references/public-surface-sync.md).
15
15
 
16
16
  ## Default Paths
17
17
 
18
18
  Use exactly one of these default paths:
19
19
 
20
- 1. Find target todos
20
+ 1. Find target todos
21
21
  `task_list`
22
22
 
23
- 2. Read one task context
23
+ 2. Read one task context
24
24
  `task_list -> exact target -> task_get`
25
25
 
26
- 3. Read associated approval context
27
- `task_get -> task_associated_report_detail_get` or `task_workflow_log_get`
26
+ 3. Read associated approval context when material to a high-impact recommendation
27
+ `task_get -> task_workflow_log_get` and/or material `task_associated_report_detail_get`
28
28
 
29
- 4. Execute workflow action
29
+ 4. Execute workflow action
30
30
  `task_list -> exact target -> task_get -> task_action_execute`
31
31
 
32
32
  5. Execute a user-specified action on an already-clear target
@@ -43,29 +43,26 @@ Use exactly one of these default paths:
43
43
  ## Supporting Tools
44
44
 
45
45
  - `app_list`
46
- - `app_search`
47
46
 
48
47
  ## Standard Operating Order
49
48
 
50
49
  Use one of these two modes:
51
50
 
52
51
  1. Recommendation mode
53
- 1. Ensure auth exists
54
- 2. Ensure workspace is selected
55
- 3. Discover the exact target with `task_list`
56
- 4. Read node context with `task_get`
57
- 5. Before giving any approval recommendation, read `task_workflow_log_get`
58
- 6. If `task_get` returns any `associated_reports`, read every visible report through `task_associated_report_detail_get`
59
- 7. Give a recommendation only after reviewing node context, workflow log, and associated reports
60
- 8. Wait for explicit user confirmation before `task_action_execute`
52
+ 1. Trust the current MCP/session when the runtime has already injected credentials; recover auth/workspace only after an actual tool error
53
+ 2. Discover the exact target with `task_list`
54
+ 3. Read node context with `task_get`
55
+ 4. Before giving a high-impact approve/reject/rollback/transfer recommendation, read `task_workflow_log_get`
56
+ 5. If `task_get` returns visible `associated_reports`, read the reports that are material to the decision; if a report cannot be read, disclose the gap instead of blocking indefinitely
57
+ 6. Give a recommendation only after reviewing the available node context, workflow log when visible, and material associated reports
58
+ 7. Wait for explicit user confirmation before `task_action_execute`
61
59
 
62
60
  2. User-directed execution mode
63
- 1. Ensure auth exists
64
- 2. Ensure workspace is selected
65
- 3. Discover the exact target with `task_list`
66
- 4. If the target or action requirements are ambiguous, read `task_get`; otherwise go straight to `task_action_execute`
67
- 5. Execute through `task_action_execute`
68
- 6. After actions, report the exact `app_key`, `record_id`, `workflow_node_id`, executed action, and any warnings
61
+ 1. Trust the current MCP/session when the runtime has already injected credentials; recover auth/workspace only after an actual tool error
62
+ 2. Discover the exact target with `task_list`
63
+ 3. If the target or action requirements are ambiguous, read `task_get`; otherwise go straight to `task_action_execute`
64
+ 4. Execute through `task_action_execute`
65
+ 5. After actions, report the exact `app_key`, `record_id`, `workflow_node_id`, executed action, and any warnings
69
66
 
70
67
  ## Task-Center Rules
71
68
 
@@ -106,15 +103,18 @@ Use one of these two modes:
106
103
  - `transfer`
107
104
  - `urge`
108
105
  - `save_only`
109
- - Before any approve/reject/rollback/transfer recommendation, always review `task_workflow_log_get` when `task_get.visibility.audit_record_visible=true`
110
- - If `task_get` returns visible `associated_reports`, review each one with `task_associated_report_detail_get`; do not rely on report summary alone
111
- - Do not give an approval recommendation based only on `task_get`
106
+ - Before high-impact approve/reject/rollback/transfer recommendations, review `task_workflow_log_get` when `task_get.visibility.audit_record_visible=true`
107
+ - If `task_get` returns visible `associated_reports`, review material reports with `task_associated_report_detail_get`; for unreadable or irrelevant reports, disclose the limitation instead of blocking the whole task
108
+ - QingBI associated report detail follows the frontend/qflow visible chart-data route first; a middle `CHART_SEE` / `40002` from legacy BI data reads is not by itself proof that the task-associated report is invisible.
109
+ - Do not give a high-impact approval recommendation based only on `task_get` unless workflow log/report visibility is unavailable and you explicitly state that limitation
112
110
  - Do not execute `task_action_execute` until the user explicitly confirms the chosen action
113
111
  - Exception: if the user has already explicitly authorized a concrete action on exact targets, you may execute directly after exact target resolution
114
112
  - Avoid actions on ambiguous tasks or records
115
113
  - Summarize the final action and the exact `app_key / record_id / workflow_node_id`
116
114
  - `reject` requires `payload.audit_feedback`
115
+ - For approve/reject, trust the current task detail or an explicit frontend-provided `formId`; app baseInfo is only a fallback. A baseInfo `40002` is not final task-action denial when `formId` is already known.
117
116
  - `save_only` requires non-empty `fields` and is only available when the backend exposes editable fields for the current node
117
+ - For `save_only`, trust `task_get.editable_fields` / `editableQueIds` from the current task node. An app applicant-schema `40002` is not final task denial when the task detail already exposes the editable field.
118
118
  - `task_action_execute` now distinguishes action execution from workflow continuation. Read `verification.runtime_continuation_verified` before claiming the workflow actually moved on.
119
119
  - If `task_action_execute` returns `partial_success` with `WORKFLOW_CONTINUATION_UNVERIFIED`, report the action as sent but the downstream continuation as unverified.
120
120
  - If `task_action_execute` returns `TASK_CONTEXT_VISIBILITY_UNVERIFIED` after a `46001`-style context loss, do not claim the task was already processed unless the workflow log or record state proves it.
@@ -41,4 +41,3 @@ For task ops, always report:
41
41
  - target app or task box
42
42
  - operation type: read, comment, approve, reject, rollback, transfer, urge, or mark_read
43
43
  - affected task ids or record ids
44
-
@@ -14,14 +14,12 @@ Examples:
14
14
 
15
15
  Rules:
16
16
 
17
- - if the user starts from inbox, todo, workload, cc, or bottleneck language, use `task_*` first
18
- - use `task_summary` for headline counts
19
- - use `task_list` for flat browsing
20
- - use `task_facets` when worksheet or workflow-node buckets matter
17
+ - if the user starts from inbox, todo, workload, cc, or bottleneck language, use the public task tools first
18
+ - use `task_list` for headline counts and flat browsing; group locally by app or workflow node when buckets matter
19
+ - use `task_get`, `task_workflow_log_get`, and `task_associated_report_detail_get` only after locating the exact task
21
20
  - treat task counts as task-center counts, not record counts
22
21
  - switch to `record_get` only after locating the exact business record behind a task
23
22
  - identify the exact target first
24
- - for approve or reject, identify the exact `workflow_node_id` first; prefer task-center results or current audit info, then use `task_approve` or `task_reject`
23
+ - for approve or reject, identify the exact target first; prefer `task_id` from task-center results, then use `task_action_execute` with action `approve` or `reject`
25
24
  - avoid usage-side workflow actions on ambiguous records
26
25
  - summarize the final action and target task ids or record ids
27
-
@@ -1,5 +1,9 @@
1
- """Entry point for running qingflow_mcp as a module."""
2
- from qingflow_mcp.server import main
1
+ """Entry point for running qingflow_mcp as a module.
2
+
3
+ Default to the curated user-facing MCP surface. Split package and console
4
+ entrypoints can still start the builder server explicitly.
5
+ """
6
+ from qingflow_mcp.server_app_user import main
3
7
 
4
8
  if __name__ == "__main__":
5
9
  main()
@@ -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
 
@@ -2038,6 +2072,7 @@ class AppGetResponse(StrictModel):
2038
2072
  name: str | None = None
2039
2073
  title: str | None = None
2040
2074
  app_icon: str | None = None
2075
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2041
2076
  visibility: dict[str, Any] = Field(default_factory=dict)
2042
2077
  tag_ids: list[int] = Field(default_factory=list)
2043
2078
  publish_status: int | None = None
@@ -2062,6 +2097,8 @@ class AppGetFieldsResponse(StrictModel):
2062
2097
  app_key: str
2063
2098
  fields: list[dict[str, Any]] = Field(default_factory=list)
2064
2099
  field_count: int = 0
2100
+ chart_fields: list[dict[str, Any]] = Field(default_factory=list)
2101
+ chart_field_count: int = 0
2065
2102
  form_settings: dict[str, Any] = Field(default_factory=dict)
2066
2103
 
2067
2104
 
@@ -2109,6 +2146,7 @@ class PortalReadSummaryResponse(StrictModel):
2109
2146
  dash_name: str | None = None
2110
2147
  package_tag_ids: list[int] = Field(default_factory=list)
2111
2148
  dash_icon: str | None = None
2149
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2112
2150
  hide_copyright: bool | None = None
2113
2151
  config_keys: list[str] = Field(default_factory=list)
2114
2152
  dash_global_config_keys: list[str] = Field(default_factory=list)
@@ -2122,6 +2160,7 @@ class PortalGetResponse(StrictModel):
2122
2160
  dash_name: str | None = None
2123
2161
  package_tag_ids: list[int] = Field(default_factory=list)
2124
2162
  dash_icon: str | None = None
2163
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2125
2164
  hide_copyright: bool | None = None
2126
2165
  visibility: dict[str, Any] = Field(default_factory=dict)
2127
2166
  auth: dict[str, Any] = Field(default_factory=dict)