@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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/cli/commands/exports.py +8 -0
- package/src/qingflow_mcp/server.py +2 -1
- package/src/qingflow_mcp/server_app_user.py +6 -1
- package/src/qingflow_mcp/tools/export_tools.py +28 -7
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.
|
|
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.
|
|
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
package/pyproject.toml
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
)
|