@josephyan/qingflow-app-builder-mcp 0.2.0-beta.1012 → 0.2.0-beta.1013

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.1012
6
+ npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.1013
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.1012 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.1013 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.1012",
3
+ "version": "0.2.0-beta.1013",
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.0b1012"
7
+ version = "0.2.0b1013"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  __all__ = ["__version__"]
7
7
 
8
- _FALLBACK_VERSION = "0.2.0b1012"
8
+ _FALLBACK_VERSION = "0.2.0b1013"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None:
@@ -16,6 +16,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
16
16
  start.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
17
17
  start.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
18
18
  start.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
19
+ start.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
19
20
  start.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
20
21
  start.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
21
22
  start.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
@@ -36,6 +37,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
36
37
  direct.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
37
38
  direct.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
38
39
  direct.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
40
+ direct.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
39
41
  direct.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
40
42
  direct.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
41
43
  direct.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
@@ -62,6 +64,10 @@ def _where(args: argparse.Namespace) -> list[dict]:
62
64
  return load_list_arg(args.where_file, option_name="--where-file") if args.where_file else []
63
65
 
64
66
 
67
+ def _order_by(args: argparse.Namespace) -> list[dict]:
68
+ return load_list_arg(args.order_by_file, option_name="--order-by-file") if args.order_by_file else []
69
+
70
+
65
71
  def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
66
72
  return context.exports.record_export_start(
67
73
  profile=args.profile,
@@ -69,6 +75,7 @@ def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
69
75
  view_id=args.view_id,
70
76
  columns=_columns(args),
71
77
  where=_where(args),
78
+ order_by=_order_by(args),
72
79
  record_ids=_record_ids(args),
73
80
  include_workflow_log=args.include_workflow_log,
74
81
  )
@@ -96,6 +103,7 @@ def _handle_direct(args: argparse.Namespace, context: CliContext) -> dict:
96
103
  view_id=args.view_id,
97
104
  columns=_columns(args),
98
105
  where=_where(args),
106
+ order_by=_order_by(args),
99
107
  record_ids=_record_ids(args),
100
108
  include_workflow_log=args.include_workflow_log,
101
109
  download_to_path=args.download_to_path,
@@ -160,8 +160,9 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
160
160
  - pass `record_ids` to export selected rows only
161
161
  - `record_export_start` / `record_export_direct` also support internal query selection:
162
162
  - pass `where` to resolve matching `record_id` values first
163
+ - pass `order_by` to keep the internal query and export row order aligned with `record_list`
163
164
  - then run native export as selected rows
164
- - `where` and `record_ids` are mutually exclusive
165
+ - `where/order_by` and `record_ids` are mutually exclusive
165
166
  - `record_export_start` / `record_export_direct` also support frontend-like column selection:
166
167
  - omit `columns` to export all current-view fields
167
168
  - pass `columns` to export only selected fields, preserving the provided order
@@ -155,8 +155,9 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
155
155
  - pass `record_ids` to export selected rows only
156
156
  - `record_export_start` / `record_export_direct` also support internal query selection:
157
157
  - pass `where` to resolve matching `record_id` values first
158
+ - pass `order_by` to keep the internal query and export row order aligned with `record_list`
158
159
  - then run native export as selected rows
159
- - `where` and `record_ids` are mutually exclusive
160
+ - `where/order_by` and `record_ids` are mutually exclusive
160
161
  - `record_export_start` / `record_export_direct` also support frontend-like column selection:
161
162
  - omit `columns` to export all current-view fields
162
163
  - pass `columns` to export only selected fields, preserving the provided order
@@ -246,6 +247,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
246
247
  view_id: str = "system:all",
247
248
  columns: list[dict | int] | None = None,
248
249
  where: list[dict] | None = None,
250
+ order_by: list[dict] | None = None,
249
251
  record_ids: list[str | int] | None = None,
250
252
  include_workflow_log: bool = False,
251
253
  ) -> dict:
@@ -255,6 +257,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
255
257
  view_id=view_id,
256
258
  columns=columns or [],
257
259
  where=where or [],
260
+ order_by=order_by or [],
258
261
  record_ids=record_ids or [],
259
262
  include_workflow_log=include_workflow_log,
260
263
  )
@@ -285,6 +288,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
285
288
  view_id: str = "system:all",
286
289
  columns: list[dict | int] | None = None,
287
290
  where: list[dict] | None = None,
291
+ order_by: list[dict] | None = None,
288
292
  record_ids: list[str | int] | None = None,
289
293
  include_workflow_log: bool = False,
290
294
  download_to_path: str | None = None,
@@ -296,6 +300,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
296
300
  view_id=view_id,
297
301
  columns=columns or [],
298
302
  where=where or [],
303
+ order_by=order_by or [],
299
304
  record_ids=record_ids or [],
300
305
  include_workflow_log=include_workflow_log,
301
306
  download_to_path=download_to_path,
@@ -60,6 +60,7 @@ class ExportTools(ToolBase):
60
60
  view_id: str = "system:all",
61
61
  columns: list[JSONObject | int] | None = None,
62
62
  where: list[JSONObject] | None = None,
63
+ order_by: list[JSONObject] | None = None,
63
64
  record_ids: list[str | int] | None = None,
64
65
  include_workflow_log: bool = False,
65
66
  ) -> dict[str, Any]:
@@ -69,6 +70,7 @@ class ExportTools(ToolBase):
69
70
  view_id=view_id,
70
71
  columns=columns or [],
71
72
  where=where or [],
73
+ order_by=order_by or [],
72
74
  record_ids=record_ids or [],
73
75
  include_workflow_log=include_workflow_log,
74
76
  )
@@ -102,6 +104,7 @@ class ExportTools(ToolBase):
102
104
  view_id: str = "system:all",
103
105
  columns: list[JSONObject | int] | None = None,
104
106
  where: list[JSONObject] | None = None,
107
+ order_by: list[JSONObject] | None = None,
105
108
  record_ids: list[str | int] | None = None,
106
109
  include_workflow_log: bool = False,
107
110
  download_to_path: str | None = None,
@@ -113,6 +116,7 @@ class ExportTools(ToolBase):
113
116
  view_id=view_id,
114
117
  columns=columns or [],
115
118
  where=where or [],
119
+ order_by=order_by or [],
116
120
  record_ids=record_ids or [],
117
121
  include_workflow_log=include_workflow_log,
118
122
  download_to_path=download_to_path,
@@ -128,6 +132,7 @@ class ExportTools(ToolBase):
128
132
  view_id: str = "system:all",
129
133
  columns: list[JSONObject | int] | None = None,
130
134
  where: list[JSONObject] | None = None,
135
+ order_by: list[JSONObject] | None = None,
131
136
  record_ids: list[str | int] | None = None,
132
137
  include_workflow_log: bool = False,
133
138
  ) -> dict[str, Any]:
@@ -135,6 +140,7 @@ class ExportTools(ToolBase):
135
140
  normalized_view_id = str(view_id or "").strip() or "system:all"
136
141
  normalized_columns = _normalize_export_columns(columns or [])
137
142
  normalized_where = self._record_tools._normalize_record_list_where(where or [])
143
+ normalized_order_by = self._record_tools._normalize_record_list_order_by(order_by or [])
138
144
  normalized_record_ids = _normalize_export_record_ids(record_ids or [])
139
145
  if not normalized_app_key:
140
146
  return self._failed_export_result(
@@ -168,6 +174,7 @@ class ExportTools(ToolBase):
168
174
  resolved_view=resolved_view,
169
175
  selected_record_ids=normalized_record_ids,
170
176
  where_filters=normalized_where,
177
+ order_by=normalized_order_by,
171
178
  select_columns=cast(list[JSONObject], export_config.get("questionExportConfigList") or []),
172
179
  )
173
180
  result_amount = self._estimate_export_result_amount(
@@ -240,6 +247,7 @@ class ExportTools(ToolBase):
240
247
  "view_route_supported": True,
241
248
  "row_selection_applied": bool(effective_record_ids),
242
249
  "query_filter_applied": bool(normalized_where),
250
+ "query_sort_applied": bool(normalized_order_by),
243
251
  "field_selection_applied": bool(normalized_columns),
244
252
  },
245
253
  "request_route": self.backend.describe_route(context),
@@ -424,6 +432,7 @@ class ExportTools(ToolBase):
424
432
  view_id: str = "system:all",
425
433
  columns: list[JSONObject | int] | None = None,
426
434
  where: list[JSONObject] | None = None,
435
+ order_by: list[JSONObject] | None = None,
427
436
  record_ids: list[str | int] | None = None,
428
437
  include_workflow_log: bool = False,
429
438
  download_to_path: str | None = None,
@@ -446,6 +455,7 @@ class ExportTools(ToolBase):
446
455
  view_id=normalized_view_id,
447
456
  columns=columns or [],
448
457
  where=where or [],
458
+ order_by=order_by or [],
449
459
  record_ids=record_ids or [],
450
460
  include_workflow_log=include_workflow_log,
451
461
  )
@@ -579,27 +589,29 @@ class ExportTools(ToolBase):
579
589
  resolved_view: AccessibleViewRoute,
580
590
  selected_record_ids: list[int],
581
591
  where_filters: list[JSONObject],
592
+ order_by: list[JSONObject],
582
593
  select_columns: list[JSONObject],
583
594
  ) -> tuple[list[int], str]:
584
- if selected_record_ids and where_filters:
595
+ if selected_record_ids and (where_filters or order_by):
585
596
  raise QingflowApiError(
586
597
  category="config",
587
- message="record export does not allow record_ids and where at the same time",
598
+ message="record export does not allow record_ids together with query selectors",
588
599
  details={
589
600
  "error_code": "EXPORT_ROW_SCOPE_CONFLICT",
590
- "fix_hint": "Use record_ids for explicit selected rows or where for internal query selection, but not both.",
601
+ "fix_hint": "Use record_ids for explicit selected rows, or use where/order_by for internal query selection, but not both.",
591
602
  },
592
603
  )
593
604
  if selected_record_ids:
594
605
  return selected_record_ids, "selected"
595
- if not where_filters:
606
+ if not where_filters and not order_by:
596
607
  return [], "all"
597
- resolved_ids = self._collect_record_ids_from_where(
608
+ resolved_ids = self._collect_record_ids_from_query(
598
609
  profile=profile,
599
610
  context=context,
600
611
  app_key=app_key,
601
612
  resolved_view=resolved_view,
602
613
  where_filters=where_filters,
614
+ order_by=order_by,
603
615
  select_columns=select_columns,
604
616
  )
605
617
  if not resolved_ids:
@@ -612,7 +624,7 @@ class ExportTools(ToolBase):
612
624
  )
613
625
  return resolved_ids, "queried"
614
626
 
615
- def _collect_record_ids_from_where(
627
+ def _collect_record_ids_from_query(
616
628
  self,
617
629
  *,
618
630
  profile: str,
@@ -620,6 +632,7 @@ class ExportTools(ToolBase):
620
632
  app_key: str,
621
633
  resolved_view: AccessibleViewRoute,
622
634
  where_filters: list[JSONObject],
635
+ order_by: list[JSONObject],
623
636
  select_columns: list[JSONObject],
624
637
  ) -> list[int]:
625
638
  browse_scope = self._record_tools._build_browse_write_scope(
@@ -631,6 +644,14 @@ class ExportTools(ToolBase):
631
644
  )
632
645
  index = browse_scope["index"]
633
646
  match_rules = self._record_tools._resolve_match_rules(context, where_filters, index)
647
+ query_sorts = [
648
+ {
649
+ "queId": _coerce_int(item.get("field_id")),
650
+ "direction": str(item.get("direction") or "asc"),
651
+ }
652
+ for item in order_by
653
+ if isinstance(item, dict) and _coerce_int(item.get("field_id")) is not None
654
+ ]
634
655
  dept_member_cache: dict[int, set[int]] = {}
635
656
  current_page = 1
636
657
  selected_ids: list[int] = []
@@ -651,7 +672,7 @@ class ExportTools(ToolBase):
651
672
  page_size=DEFAULT_LIST_PAGE_SIZE,
652
673
  query_key=None,
653
674
  match_rules=match_rules,
654
- sorts=[],
675
+ sorts=cast(list[JSONObject], query_sorts),
655
676
  search_que_ids=primary_search_que_ids,
656
677
  list_type=resolved_view.list_type if resolved_view.list_type is not None else DEFAULT_RECORD_LIST_TYPE,
657
678
  )