@josephyan/qingflow-app-user-mcp 0.2.0-beta.16 → 0.2.0-beta.18

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-user-mcp@0.2.0-beta.16
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.18
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.16 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.18 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "0.2.0-beta.16",
3
+ "version": "0.2.0-beta.18",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory 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.0b16"
7
+ version = "0.2.0b18"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,8 +21,8 @@ When the task is in `prod`, browser parity matters, or the user says "the page h
21
21
  Primary record and data tools:
22
22
 
23
23
  - `record_query`
24
+ - `record_schema_get`
24
25
  - `record_write_plan`
25
- - `record_field_resolve`
26
26
  - `record_create`
27
27
  - `record_get`
28
28
  - `record_update`
@@ -90,9 +90,10 @@ Do not use builder-side tools here:
90
90
  - Use `task_statistics` before `task_list` when the user only needs counts
91
91
  - Use `task_list_grouped` when worksheet or group buckets matter
92
92
  - Use `task_urge` only when the user clearly wants a reminder sent for a pending task
93
- - Use `record_field_resolve` when field selectors are ambiguous; if the task then turns into analysis, switch to `$qingflow-record-analysis`
93
+ - Use `record_schema_get` when field selectors are ambiguous; if the task then turns into analysis, switch to `$qingflow-record-analysis`
94
94
  - For precise record lookup, use `record_get` when `apply_id` is known
95
- - Use `record_field_resolve` when the user gives field titles and you are not fully sure about the exact schema; do not guess ambiguous fields silently
95
+ - Use `record_schema_get` when the user gives field titles and you are not fully sure about the exact schema; do not guess ambiguous fields silently
96
+ - If the task has already shifted into analysis and `record_schema_get` still leaves multiple plausible fields, stop and ask the user to confirm the intended field instead of continuing to try read tools in a loop
96
97
  - Treat field selectors as schema-first and platform-generic. Prefer exact field titles, then neutral aliases such as `创建时间`, `新增时间`, `负责人`, `部门`, `时间`, or `阶段` only when the tool resolves them clearly. Do not assume CRM shorthand like `销售`, `商机阶段`, `客户全称`, or similar domain shortcuts apply across arbitrary Qingflow apps
97
98
  - For updates, inspect current data first unless the user already provided the exact target and patch
98
99
  - For deletes, confirm the exact record scope and report the deleted ids
@@ -124,9 +125,9 @@ When the user asks for demo data, seed, smoke data, or mock data:
124
125
 
125
126
  ## Response Interpretation
126
127
 
127
- - `record_query(summary)` and `record_aggregate` expose `completeness`; do not treat partial scans as final conclusions
128
- - `record_query(summary)` and `record_aggregate` now also expose `analysis_status`, `safe_for_final_conclusion`, and `analysis_counts`; if `status=partial_success` or `safe_for_final_conclusion=false`, do not present the result as final
129
- - For aggregate or summary answers, report both `backend_total_count` and `scanned_count` when coverage matters; for full analysis framing, switch to `$qingflow-record-analysis`
128
+ - `record_query(query_mode="list")` is browse/sample output, not a final analysis result
129
+ - If `record_query(query_mode="list")` reports `row_cap_hit`, `sample_only`, or capped rows, do not present it as full data
130
+ - For grouped distributions, trends, or final statistical conclusions, switch to `$qingflow-record-analysis` and use `record_schema_get -> record_analyze`
130
131
  - `record_write_plan` is static preflight, not a guarantee that submit will pass runtime linkage or visibility checks
131
132
  - `record_create` now returns integer `apply_id`; you can pass that id directly into `record_get`, `record_update`, or `record_delete`
132
133
  - `verify_write=true` means the tool read the record back and compared the written fields; if it returns `status=verification_failed` or `ok=false`, do not report the create or update as successful
@@ -5,14 +5,13 @@ For final statistics, grouped distributions, or insight-style analysis, use [$qi
5
5
  ## Counts
6
6
 
7
7
  - Prefer `effective_count`
8
- - For `record_query(summary)` and `record_aggregate`, inspect `completeness`, `analysis_status`, and `safe_for_final_conclusion` before concluding
9
- - If `status=partial_success`, treat the result as exploratory unless the user explicitly asked for a partial sample
8
+ - For final analysis, inspect `record_analyze.data.completeness` and `safe_for_final_conclusion` before concluding
9
+ - If `record_analyze.status!=success`, treat the result as exploratory unless the user explicitly asked for a partial sample
10
10
  - `record_query(list)` is for browsing and sample inspection. If it reports `row_cap_hit`, `sample_only`, or capped `returned_items`, do not present it as full data
11
11
  - When coverage matters, surface:
12
12
  - `backend_total_count`
13
13
  - `scanned_count`
14
- - `unscanned_count`
15
- - Reuse `suggested_next_call` or `estimate.recommended_arguments` instead of inventing bigger scan settings by hand
14
+ - Use narrower views, filters, or smaller analysis questions instead of inventing manual scan settings by hand
16
15
  - If the browser and MCP disagree, compare `request_route.base_url` and `request_route.qf_version` first
17
16
  - Do not mix a full aggregate total with sample-only list detail in one sentence like “基于全部数据分析”; split the answer into `全量结论` and `样本观察`
18
17
 
@@ -25,8 +24,8 @@ For final statistics, grouped distributions, or insight-style analysis, use [$qi
25
24
 
26
25
  - `record_write_plan` is static preflight only; linked visibility and runtime required rules can still reject writes
27
26
  - `record_write_plan` now exposes `write_format.support_level`; check `full / restricted / unsupported` before attempting non-trivial writes
28
- - Use `record_field_resolve` when field titles are uncertain instead of guessing ids
29
- - For analysis tasks, use the fixed preflight order `record_field_resolve -> record_query_plan -> summary/aggregate`; do not switch tools blindly after `FIELD_NOT_FOUND` or ambiguity
27
+ - Use `record_schema_get` when field titles are uncertain instead of guessing ids
28
+ - For analysis tasks, use the fixed path `record_schema_get -> record_analyze`; do not switch tools blindly after `FIELD_NOT_FOUND` or ambiguity
30
29
  - Prefer `strict_full=true` for final statistics or business conclusions
31
30
  - `record_create` and `record_update` can do post-write verification with `verify_write=true`; use that for complex, subtable, or production writes
32
31
  - `apply_id` is normalized to an integer; pass it directly into later record tools
@@ -9,9 +9,9 @@ Use `record_query` first when:
9
9
  - the user only gives a title or business key
10
10
  - the target record id is unknown
11
11
  - updates or deletes need confirmation
12
- - summary analysis or final counts are needed
12
+ - ordinary list browsing or spot checks are needed
13
13
 
14
- Use `record_query_plan` first when:
14
+ Use [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md) when:
15
15
 
16
16
  - field titles may be ambiguous
17
17
  - filters are still in natural-language shape
@@ -22,14 +22,13 @@ Use `record_query_plan` first when:
22
22
 
23
23
  ## Final analysis pattern
24
24
 
25
- 1. Run `record_query_plan`
26
- 2. If the plan exposes `estimate.recommended_arguments` or `suggested_next_call`, prefer those arguments directly
27
- 3. Run `record_query(query_mode="summary", strict_full=true, auto_expand_pages=true)` to confirm the total scope
28
- 4. Run `record_aggregate(strict_full=true, auto_expand_pages=true)` for grouped results
29
- 5. Run `record_query(query_mode="list")` only if you still need sample rows or examples
30
- 6. Report `backend_total_count`, `scanned_count`, and whether the result is safe for a final conclusion
31
- 7. If `status=partial_success` or `safe_for_final_conclusion=false`, stop at “partial result” instead of presenting a final business conclusion
32
- 8. If list rows are sample-only, separate the answer into:
25
+ 1. Run `record_schema_get`
26
+ 2. Generate one or more field_id-based DSLs
27
+ 3. Run `record_analyze(strict_full=true)` for summary/distribution/trend/cross analysis
28
+ 4. Run `record_query(query_mode="list")` only if you still need sample rows or examples
29
+ 5. Report `backend_total_count`, `scanned_count`, and whether the result is safe for a final conclusion
30
+ 6. If `status=error` or `safe_for_final_conclusion=false`, stop at “partial result instead of presenting a final business conclusion
31
+ 7. If list rows are sample-only, separate the answer into:
33
32
  - `全量可信结论`
34
33
  - `样本观察(不作为最终结论)`
35
34
  - optional `待验证假设`
@@ -42,12 +41,12 @@ Do not do this:
42
41
  2. Get `200` rows back
43
42
  3. Report平均值、占比、地域分布 as if they were based on all records
44
43
 
45
- This is not acceptable because the list endpoint can be capped. Use `record_query_plan -> summary -> aggregate` first, then treat list rows as sample-only evidence.
44
+ This is not acceptable because the list endpoint can be capped. Use `record_schema_get -> record_analyze` first, then treat list rows as sample-only evidence.
46
45
 
47
46
  ## Create pattern
48
47
 
49
48
  1. Confirm target app
50
- 2. Resolve fields with `record_field_resolve` if needed. Prefer exact schema titles first; only rely on platform-neutral aliases such as `创建时间`, `负责人`, or `部门` when they resolve cleanly, and do not assume business-domain shorthand like `销售` is portable across apps
49
+ 2. Resolve fields with `record_schema_get` if needed. Prefer exact schema titles first; only rely on platform-neutral aliases such as `创建时间`, `负责人`, or `部门` when they resolve cleanly, and do not assume business-domain shorthand like `销售` is portable across apps
51
50
  3. Run `record_write_plan` for non-trivial payloads or any `fields`-based write
52
51
  4. For relation fields, query the target app first and resolve the referenced record `apply_id`
53
52
  5. For attachments, call `file_upload_local` first and reuse the returned `attachment_value`
@@ -2,7 +2,7 @@
2
2
  name: qingflow-record-analysis
3
3
  description: Analyze Qingflow record data safely after the MCP is already connected and authenticated. Use when the user wants grouped distributions, ratios, averages, rankings, trends, insights, or any final statistical conclusion across an existing app's data. Do not use this skill for schema changes, app design, or ordinary record CRUD unless they are strictly supporting an analysis flow.
4
4
  metadata:
5
- short-description: Analyze Qingflow record data with plan-first, sample-safe reporting
5
+ short-description: Analyze Qingflow record data with schema-first DSL execution
6
6
  ---
7
7
 
8
8
  # Qingflow Record Analysis
@@ -19,47 +19,194 @@ Before running analysis in `prod`, confirm the intended environment and compare
19
19
 
20
20
  Use these tools as the core analysis surface:
21
21
 
22
- - `record_field_resolve`
23
- - `record_query_plan`
24
- - `record_query`
25
- - `record_aggregate`
22
+ - `record_schema_get`
23
+ - `record_analyze`
26
24
 
27
- Use `record_get` or ordinary `record_query(list)` only when you need sample rows or a specific supporting example after the main analysis path.
25
+ Use `record_query(query_mode="list")` or `record_get` only when you need sample rows or a specific supporting example after the main analysis path.
28
26
 
29
27
  ## Hard Rules
30
28
 
31
- - Analysis tasks must start with `record_query_plan`
32
- - If fields are uncertain, the fixed order is:
33
- 1. `record_field_resolve`
34
- 2. `record_query_plan`
35
- 3. `record_query(summary)` and/or `record_aggregate`
29
+ - Analysis tasks must start with `record_schema_get`
30
+ - Build one or more small DSLs, then run `record_analyze` separately for each question
31
+ - DSL field references must use `field_id` only
32
+ - If the schema still leaves multiple plausible fields, stop and ask the user to confirm from a short candidate list instead of guessing
33
+ - Do not keep retrying different guessed field names in a loop
36
34
  - `record_query(list)` is never the basis for a final statistical conclusion
37
35
  - If `record_query(list)` reports `row_cap_hit`, `sample_only`, capped `returned_items`, or compact output, treat it as sample-only evidence
38
- - Do not mix full totals from `summary` or `aggregate` with sample-only list observations as one combined “全量结论”
39
- - Reuse `record_query_plan.estimate.recommended_arguments` or `suggested_next_call` instead of guessing scan parameters
40
- - For final conclusions, prefer `strict_full=true` and `auto_expand_pages=true`
36
+ - Do not mix full totals from `record_analyze` with sample-only list observations as one combined `全量结论`
37
+ - Do not manually tune paging or scan-budget parameters for analysis; `record_analyze` hides them
38
+ - For final conclusions, prefer `strict_full=true`
41
39
 
42
40
  ## Standard Operating Order
43
41
 
44
42
  For analysis:
45
43
 
46
44
  1. Confirm target app and environment
47
- 2. If field names are uncertain, run `record_field_resolve`
48
- 3. Run `record_query_plan`
49
- 4. Reuse MCP-recommended arguments from `suggested_next_call` or `estimate.recommended_arguments`
50
- 5. Run `record_query(query_mode="summary", strict_full=true, auto_expand_pages=true)` to establish full scope
51
- 6. Run `record_aggregate(strict_full=true, auto_expand_pages=true)` for grouped distributions, ranking, ratios, averages, or trends
52
- 7. Run `record_query(query_mode="list")` only if you still need sample rows, examples, or manual inspection
53
- 8. Before answering, separate:
45
+ 2. Run `record_schema_get`
46
+ 3. Inspect fields, aliases, suggested dimensions, suggested metrics, and suggested time fields
47
+ 4. Generate one or more field_id-based DSLs
48
+ 5. Run `record_analyze` once per DSL
49
+ 6. Run `record_query(query_mode="list")` only if you still need sample rows, examples, or manual inspection
50
+ 7. Before answering, separate:
54
51
  - `全量可信结论`
55
52
  - `样本观察`
56
53
  - `待验证假设`
57
54
 
55
+ ## DSL Contract
56
+
57
+ Use `record_schema_get` as the source of truth for every DSL field reference:
58
+
59
+ - Use `fields[].field_id` in `dimensions[].field_id`, `metrics[].field_id`, and `filters[].field_id`
60
+ - Treat `suggested_dimensions`, `suggested_metrics`, and `suggested_time_fields` as hints, not as executable DSL by themselves
61
+ - Do not pass field titles, aliases, or guessed ids where `field_id` is required
62
+
63
+ The `record_analyze` call should be built from this argument shape:
64
+
65
+ ```json
66
+ {
67
+ "app_key": "APP_1",
68
+ "dimensions": [],
69
+ "metrics": [],
70
+ "filters": [],
71
+ "sort": [],
72
+ "limit": 50,
73
+ "strict_full": true,
74
+ "view_key": null,
75
+ "view_name": null,
76
+ "output_profile": "normal"
77
+ }
78
+ ```
79
+
80
+ Top-level argument rules:
81
+
82
+ - `app_key`: required. The target Qingflow app.
83
+ - `dimensions`: required list. Use `[]` for whole-table summary. Use one item per grouping dimension for grouped analysis.
84
+ - `metrics`: optional list. If omitted or empty, `record_analyze` defaults to a single `count` metric.
85
+ - `filters`: optional list. Filters restrict the analyzed dataset before results are interpreted.
86
+ - `sort`: optional list. Sorting applies to result rows, not raw source rows.
87
+ - `limit`: positive integer. It only limits returned result rows; it does not reduce the internal scan scope.
88
+ - `strict_full`: boolean. Prefer `true` for final conclusions. If `true`, incomplete scans return an error; if `false`, incomplete scans return partial results.
89
+ - `view_key` / `view_name`: optional. Use a view to narrow scope before analysis. Prefer `view_key` when both are available.
90
+ - `output_profile`: `normal` or `verbose`. Prefer `normal` unless you are debugging completeness or route issues.
91
+
92
+ Item contracts:
93
+
94
+ - `dimensions` item:
95
+ - shape: `{ "field_id": 2, "alias": "状态", "bucket": null }`
96
+ - `field_id`: required integer from `record_schema_get`
97
+ - `alias`: optional but recommended; if omitted, the field title becomes the alias
98
+ - `bucket`: optional; allowed values are `day`, `week`, `month`, `quarter`, `year`, or omitted / `null`
99
+ - `bucket` may only be used on fields from `suggested_time_fields`
100
+ - `metrics` item:
101
+ - shape: `{ "op": "sum", "field_id": 7, "alias": "总金额" }`
102
+ - `op`: one of `count`, `sum`, `avg`, `min`, `max`, `distinct_count`
103
+ - `field_id`: required for `sum`, `avg`, `min`, `max`, `distinct_count`; do not pass it for `count`
104
+ - `alias`: optional but strongly recommended because `sort.by` must reference aliases
105
+ - `filters` item:
106
+ - shape: `{ "field_id": 2, "op": "eq", "value": "进行中" }`
107
+ - `field_id`: required integer from `record_schema_get`
108
+ - `op`: optional; defaults to `eq`
109
+ - supported ops: `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `between`, `contains`, `is_null`, `not_null`
110
+ - value rules:
111
+ - `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`: pass a single scalar value
112
+ - `in`, `not_in`: pass an array
113
+ - `between`: pass a two-item array like `[min, max]`
114
+ - `is_null`, `not_null`: omit `value`
115
+ - `sort` item:
116
+ - shape: `{ "by": "记录数", "order": "desc" }`
117
+ - `by`: required and must reference an alias already defined in `dimensions` or `metrics`
118
+ - `order`: optional; use `asc` or `desc`; default is `asc`
119
+ - do not sort by raw field title or `field_id`
120
+
121
+ Practical rules:
122
+
123
+ - Keep one DSL focused on one question. Prefer multiple small DSLs over one overloaded request.
124
+ - Always set explicit aliases for metrics you may sort by, compare, or quote in the final answer.
125
+ - For trend analysis, use one time dimension with `bucket`, then sort by that time alias ascending.
126
+ - For cross analysis, use multiple `dimensions` and a small set of metrics.
127
+ - Do not attempt formulas, joins, having clauses, cohort analysis, or manual paging controls in this DSL.
128
+
129
+ ## Minimal DSL Templates
130
+
131
+ Summary:
132
+
133
+ ```json
134
+ {
135
+ "dimensions": [],
136
+ "metrics": [
137
+ { "op": "count", "alias": "记录数" }
138
+ ],
139
+ "filters": [],
140
+ "sort": [],
141
+ "limit": 1,
142
+ "strict_full": true
143
+ }
144
+ ```
145
+
146
+ Single-dimension distribution:
147
+
148
+ ```json
149
+ {
150
+ "dimensions": [
151
+ { "field_id": 2, "alias": "状态" }
152
+ ],
153
+ "metrics": [
154
+ { "op": "count", "alias": "记录数" }
155
+ ],
156
+ "filters": [],
157
+ "sort": [
158
+ { "by": "记录数", "order": "desc" }
159
+ ],
160
+ "limit": 50,
161
+ "strict_full": true
162
+ }
163
+ ```
164
+
165
+ Time trend:
166
+
167
+ ```json
168
+ {
169
+ "dimensions": [
170
+ { "field_id": 3, "alias": "月份", "bucket": "month" }
171
+ ],
172
+ "metrics": [
173
+ { "op": "count", "alias": "记录数" }
174
+ ],
175
+ "filters": [],
176
+ "sort": [
177
+ { "by": "月份", "order": "asc" }
178
+ ],
179
+ "limit": 24,
180
+ "strict_full": true
181
+ }
182
+ ```
183
+
184
+ Two-dimensional cross analysis:
185
+
186
+ ```json
187
+ {
188
+ "dimensions": [
189
+ { "field_id": 2, "alias": "状态" },
190
+ { "field_id": 5, "alias": "负责人" }
191
+ ],
192
+ "metrics": [
193
+ { "op": "count", "alias": "记录数" },
194
+ { "op": "sum", "field_id": 7, "alias": "总金额" }
195
+ ],
196
+ "filters": [],
197
+ "sort": [
198
+ { "by": "记录数", "order": "desc" }
199
+ ],
200
+ "limit": 100,
201
+ "strict_full": true
202
+ }
203
+ ```
204
+
58
205
  ## Output Gate
59
206
 
60
- - If `record_query_plan` was not used, downgrade the answer to `初步观察`
61
- - If `safe_for_final_conclusion=false`, do not present the result as a final conclusion
62
- - If aggregate/summary is full but list evidence is sample-only, split the answer into:
207
+ - Only write `全量可信结论` when the supporting `record_analyze` calls report `completeness.status=complete` and `safe_for_final_conclusion=true`
208
+ - If any key analysis call is incomplete, downgrade the answer to `初步观察` or `部分结果`
209
+ - If aggregate-style output is full but list evidence is sample-only, split the answer into:
63
210
  - `全量可信结论`
64
211
  - `样本观察(不作为最终结论)`
65
212
  - optional `待验证假设`
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Qingflow Record Analysis"
3
- short_description: "Analyze Qingflow record data with plan-first, sample-safe reporting"
4
- default_prompt: "Use $qingflow-record-analysis for grouped distributions, ratios, rankings, trends, and final statistical conclusions in Qingflow apps. Start with record_query_plan, treat record_query(list) as sample-only when capped, and separate full conclusions from sample observations."
3
+ short_description: "Analyze Qingflow record data with schema-first DSL execution"
4
+ default_prompt: "Use $qingflow-record-analysis for grouped distributions, ratios, rankings, trends, and final statistical conclusions in Qingflow apps. Start with record_schema_get, build one or more field_id-based DSLs, then run record_analyze. Treat record_query(query_mode=\"list\") as sample-only when capped, and separate full conclusions from sample observations."
@@ -1,18 +1,19 @@
1
1
  # Analysis Gotchas
2
2
 
3
- ## Do not skip plan
3
+ ## Do not skip schema
4
4
 
5
- If the task is analysis-style and you jump straight to `record_query(list)` or `record_aggregate`, you are already off the stable path.
5
+ If the task is analysis-style and you jump straight to `record_query(query_mode="list")` or `record_analyze`, you are already off the stable path.
6
6
 
7
7
  Correct recovery:
8
8
 
9
- 1. `record_field_resolve` if fields are uncertain
10
- 2. `record_query_plan`
11
- 3. follow the recommended chain
9
+ 1. `record_schema_get`
10
+ 2. inspect the schema and choose fields
11
+ 3. build one or more small DSLs
12
+ 4. run `record_analyze`
12
13
 
13
14
  ## Do not treat 200-row list output as full data
14
15
 
15
- `record_query(list)` can hit:
16
+ `record_query(query_mode="list")` can hit:
16
17
 
17
18
  - `row_cap=200`
18
19
  - `row_cap_hit=true`
@@ -29,9 +30,9 @@ It is not acceptable to use that result alone for:
29
30
  - 地域分布
30
31
  - “基于全部数据”的 business insight
31
32
 
32
- ## Do not mix full aggregate totals with sample rows
33
+ ## Do not mix full analyze totals with sample rows
33
34
 
34
- If summary or aggregate gives full-population coverage, but list rows are capped, do not merge them into one final statement.
35
+ If `record_analyze` gives full-population coverage, but list rows are capped, do not merge them into one final statement.
35
36
 
36
37
  Split them into:
37
38
 
@@ -45,17 +46,30 @@ If the field is uncertain:
45
46
  - do not bounce across tools
46
47
  - do not guess ids
47
48
  - do not switch from one read tool to another by trial and error
49
+ - do not keep retrying different guessed field names in a loop
48
50
 
49
51
  Correct recovery:
50
52
 
51
- 1. `record_field_resolve`
52
- 2. `record_query_plan`
53
+ 1. `record_schema_get`
54
+ 2. if several plausible candidates remain, ask the user to confirm from a short list
55
+ 3. build the DSL only after the field is clear
53
56
 
54
- ## Do not override MCP scan recommendations casually
57
+ Examples of the right recovery question:
55
58
 
56
- If `record_query_plan` returns:
59
+ - “我找到两个可能的字段:`线索来源`、`来源渠道`。你要按哪个字段统计?”
60
+ - “目前最像‘来源’的字段有这三个:`来源`、`来源渠道`、`获客来源`。请确认你要按哪个字段分析。”
57
61
 
58
- - `estimate.recommended_arguments`
59
- - `suggested_next_call`
62
+ ## Do not try to control paging manually
60
63
 
61
- reuse them first. These are safer than hand-tuning `page_size`, `requested_pages`, and `scan_max_pages` from memory.
64
+ `record_analyze` hides paging and scan budget on purpose.
65
+
66
+ - Do not invent `page_size`
67
+ - Do not invent `requested_pages`
68
+ - Do not invent `scan_max_pages`
69
+ - Do not invent `auto_expand_pages`
70
+
71
+ When the result is incomplete:
72
+
73
+ 1. narrow the scope with views or filters
74
+ 2. reduce the analysis problem into smaller DSLs
75
+ 3. keep the answer at `初步观察` or `部分结果` if completeness is still not enough
@@ -15,42 +15,47 @@ Use this skill when the user asks for:
15
15
 
16
16
  ## Canonical analysis sequence
17
17
 
18
- 1. `record_query_plan`
19
- 2. `record_query(query_mode="summary", strict_full=true, auto_expand_pages=true)`
20
- 3. `record_aggregate(strict_full=true, auto_expand_pages=true)`
18
+ 1. `record_schema_get`
19
+ 2. build one or more field_id-based DSLs
20
+ 3. `record_analyze`
21
21
  4. `record_query(query_mode="list")` only for sample inspection
22
22
 
23
23
  ## Distribution / ratio pattern
24
24
 
25
- 1. Resolve ambiguous fields with `record_field_resolve` if needed
26
- 2. Run `record_query_plan`
27
- 3. Confirm total scope with `record_query(summary)`
28
- 4. Run `record_aggregate` with `group_by`
29
- 5. Report:
25
+ 1. Run `record_schema_get`
26
+ 2. Inspect candidate fields and aliases
27
+ 3. If several plausible candidates remain, stop and ask the user to confirm the field from a short list
28
+ 4. Build a DSL with:
29
+ - one dimension
30
+ - `count`
31
+ - sort by the count alias
32
+ 5. Run `record_analyze`
33
+ 6. Report:
30
34
  - `backend_total_count`
31
35
  - `scanned_count`
32
36
  - `safe_for_final_conclusion`
33
37
 
34
38
  ## Average / ranking pattern
35
39
 
36
- 1. Run `record_query_plan`
37
- 2. Run `record_query(summary)` to confirm scan coverage
38
- 3. Run `record_aggregate` with:
39
- - `group_by`
40
- - `amount_column`
41
- - `metrics=["count","avg"]` or `["count","sum"]`
42
- 4. Use list-mode only to inspect examples after the aggregate result is understood
40
+ 1. Run `record_schema_get`
41
+ 2. Choose one dimension field and one numeric metric field
42
+ 3. Build a DSL with:
43
+ - `dimensions=[...]`
44
+ - `metrics=[count,sum]` or `metrics=[count,avg,min,max]`
45
+ 4. Run `record_analyze`
46
+ 5. Use list mode only to inspect examples after the aggregate result is understood
43
47
 
44
48
  ## Trend pattern
45
49
 
46
- 1. Run `record_query_plan`
47
- 2. Run `record_query(summary)` with a clear `time_range`
48
- 3. Run `record_aggregate` with `time_bucket`
49
- 4. Treat the result as final only if `safe_for_final_conclusion=true`
50
+ 1. Run `record_schema_get`
51
+ 2. Choose a date/time field from `suggested_time_fields`
52
+ 3. Build a DSL with `bucket=day|week|month|quarter|year`
53
+ 4. Run `record_analyze`
54
+ 5. Treat the result as final only if `safe_for_final_conclusion=true`
50
55
 
51
56
  ## Sample inspection pattern
52
57
 
53
- Only use `record_query(list)` after plan/summary/aggregate when you need:
58
+ Only use `record_query(query_mode="list")` after schema/analyze when you need:
54
59
 
55
60
  - example rows
56
61
  - spot checks
@@ -58,3 +63,14 @@ Only use `record_query(list)` after plan/summary/aggregate when you need:
58
63
  - manual inspection of records behind an aggregate bucket
59
64
 
60
65
  Never use list mode alone to justify final averages, shares, rankings, or regional distribution claims.
66
+
67
+ ## Ambiguous field recovery
68
+
69
+ If the user asks for something like “来源分布” or “类型占比” and the exact field is unclear:
70
+
71
+ 1. run `record_schema_get`
72
+ 2. inspect titles, aliases, and suggested fields
73
+ 3. if one candidate is clearly dominant, proceed
74
+ 4. if multiple candidates are still plausible, ask the user to confirm which field they want
75
+
76
+ Do not keep retrying different guessed field names in a loop.
@@ -12,30 +12,30 @@ When analysis is intended as a final answer, use this order:
12
12
 
13
13
  Only write `全量可信结论` when:
14
14
 
15
- - `record_query_plan` was used
16
- - the analysis path used `summary` and/or `aggregate`
17
- - `safe_for_final_conclusion=true`
15
+ - `record_schema_get` was used
16
+ - the analysis path used one or more `record_analyze` calls
17
+ - every key analysis result has `safe_for_final_conclusion=true`
18
18
  - the result is not just a capped list sample
19
19
 
20
20
  ## Sample observation gate
21
21
 
22
22
  Put evidence into `样本观察` when:
23
23
 
24
- - it came from `record_query(list)`
24
+ - it came from `record_query(query_mode="list")`
25
25
  - the tool reports `row_cap_hit`
26
26
  - the tool reports `sample_only`
27
27
  - the result is compact/capped and not complete
28
28
 
29
29
  ## Downgrade rule
30
30
 
31
- If `record_query_plan` was not used for an analysis task, downgrade the overall framing to `初步观察` instead of `洞察` or `结论`.
31
+ If `record_schema_get` was not used for an analysis task, downgrade the overall framing to `初步观察` instead of `洞察` or `结论`.
32
32
 
33
33
  ## Anti-mixing rule
34
34
 
35
35
  Do not combine:
36
36
 
37
- - full totals from `record_query(summary)` or `record_aggregate`
38
- - sample-only details from `record_query(list)`
37
+ - full totals from `record_analyze`
38
+ - sample-only details from `record_query(query_mode="list")`
39
39
 
40
40
  into one sentence like “基于全部数据分析...”.
41
41
 
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b16"
5
+ __version__ = "0.2.0b18"
@@ -28,6 +28,8 @@ def build_server() -> FastMCP:
28
28
  instructions=(
29
29
  "Use auth_login first, then workspace_list and workspace_select. "
30
30
  "All resource tools operate with the logged-in user's Qingflow permissions.\n\n"
31
+ "For analytics, use record_schema_get first, let the model build field_id-based DSL, "
32
+ "then call record_analyze. Use record_query for list/detail browsing only.\n\n"
31
33
  "Task Center (待办/已办) handling:\n"
32
34
  "- Use task_statistics to get counts of pending tasks (todo_count), timeouts, urged, etc.\n"
33
35
  "- Use task_list to query tasks. Type values: 1=todo (待办), 2=initiated (我发起的), 3=cc (抄送), 5=done (已办).\n"
@@ -18,8 +18,9 @@ def build_user_server() -> FastMCP:
18
18
  server = FastMCP(
19
19
  "Qingflow App User MCP",
20
20
  instructions=(
21
- "Use this server for Qingflow record queries, record writes, task center operations, "
22
- "directory lookups, and approval actions. Avoid builder-side app or schema changes here."
21
+ "Use this server for Qingflow record queries, schema-first analytics via record_schema_get and record_analyze, "
22
+ "record writes, task center operations, directory lookups, and approval actions. "
23
+ "Use record_query for list/detail reads only. Avoid builder-side app or schema changes here."
23
24
  ),
24
25
  )
25
26
  sessions = SessionStore()