@qingflow-tech/qingflow-app-builder-mcp 1.0.16 → 1.0.17

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 @qingflow-tech/qingflow-app-builder-mcp@1.0.16
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.17
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.16 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.17 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
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 = "1.0.16"
7
+ version = "1.0.17"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -110,6 +110,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
110
110
  list_parser.add_argument("--where-file")
111
111
  list_parser.add_argument("--order-by-file")
112
112
  list_parser.add_argument("--page", type=int, default=1)
113
+ list_parser.add_argument("--page-size", type=int, default=10)
113
114
  list_parser.add_argument("--view-id")
114
115
  list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
115
116
  list_parser.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
@@ -367,6 +368,7 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
367
368
  where=load_list_arg(args.where_file, option_name="--where-file"),
368
369
  order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
369
370
  page=args.page,
371
+ page_size=args.page_size,
370
372
  view_id=args.view_id,
371
373
  )
372
374
 
@@ -47,20 +47,14 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
47
47
  list_parser.add_argument("--page-size", type=int, default=20)
48
48
  list_parser.set_defaults(handler=_handle_list, format_hint="task_list")
49
49
 
50
- get = task_subparsers.add_parser("get", help="读取待办详情;推荐直接传 --task-id")
51
- get.add_argument("--task-id")
52
- get.add_argument("--app-key")
53
- get.add_argument("--record-id")
54
- get.add_argument("--workflow-node-id", type=int)
50
+ get = task_subparsers.add_parser("get", help="读取待办详情;--task-id 必须来自 task list 的 data.items[].task_id")
51
+ get.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
55
52
  get.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
56
53
  get.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
57
- get.set_defaults(handler=_handle_get, format_hint="task_get")
54
+ get.set_defaults(handler=_handle_get, format_hint="task_get", app_key="", record_id="", workflow_node_id=0)
58
55
 
59
- action = task_subparsers.add_parser("action", help="执行待办动作")
60
- action.add_argument("--task-id")
61
- action.add_argument("--app-key")
62
- action.add_argument("--record-id")
63
- action.add_argument("--workflow-node-id", type=int)
56
+ action = task_subparsers.add_parser("action", help="执行待办动作;--task-id 必须来自 task list 的 data.items[].task_id")
57
+ action.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
64
58
  action.add_argument("--action", help="不传时在交互终端中选择当前待办可执行动作")
65
59
  action.add_argument("--payload-file")
66
60
  action.add_argument("--fields-file")
@@ -68,24 +62,27 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
68
62
  handler=_handle_action,
69
63
  format_hint="task_action_execute",
70
64
  hide_effective_context_line=True,
65
+ app_key="",
66
+ record_id="",
67
+ workflow_node_id=0,
71
68
  )
72
69
 
73
- report = task_subparsers.add_parser("report", help="读取待办关联报表详情;推荐直接传 --task-id")
74
- report.add_argument("--task-id")
75
- report.add_argument("--app-key")
76
- report.add_argument("--record-id")
77
- report.add_argument("--workflow-node-id", type=int)
70
+ report = task_subparsers.add_parser("report", help="读取待办关联报表详情;--task-id 必须来自 task list 的 data.items[].task_id")
71
+ report.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
78
72
  report.add_argument("--report-id", type=int, help="不传时在交互终端中选择关联报表")
79
73
  report.add_argument("--page", type=int, default=1)
80
74
  report.add_argument("--page-size", type=int, default=20)
81
- report.set_defaults(handler=_handle_report, format_hint="task_associated_report_detail_get")
75
+ report.set_defaults(
76
+ handler=_handle_report,
77
+ format_hint="task_associated_report_detail_get",
78
+ app_key="",
79
+ record_id="",
80
+ workflow_node_id=0,
81
+ )
82
82
 
83
- log = task_subparsers.add_parser("log", help="读取流程日志;推荐直接传 --task-id")
84
- log.add_argument("--task-id")
85
- log.add_argument("--app-key")
86
- log.add_argument("--record-id")
87
- log.add_argument("--workflow-node-id", type=int)
88
- log.set_defaults(handler=_handle_log, format_hint="")
83
+ log = task_subparsers.add_parser("log", help="读取流程日志;--task-id 必须来自 task list 的 data.items[].task_id")
84
+ log.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
85
+ log.set_defaults(handler=_handle_log, format_hint="", app_key="", record_id="", workflow_node_id=0)
89
86
 
90
87
 
91
88
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
@@ -243,30 +240,20 @@ def _run_task_workbench_task_loop(args: argparse.Namespace, context: CliContext)
243
240
  def _resolve_task_locator_or_select(args: argparse.Namespace, context: CliContext, *, tool_name: str) -> dict | None:
244
241
  if (args.task_id or "").strip():
245
242
  return None
246
- has_app_key = bool((args.app_key or "").strip())
247
- has_record_id = bool((args.record_id or "").strip())
248
- has_workflow_node_id = int(args.workflow_node_id or 0) > 0
249
- if has_app_key and has_record_id and has_workflow_node_id:
250
- return None
251
- if has_app_key or has_record_id or has_workflow_node_id:
252
- raise_config_error(
253
- f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id",
254
- fix_hint="Either pass `--task-id TASK_ID`, or provide the full locator triple `--app-key --record-id --workflow-node-id`.",
255
- )
256
243
 
257
244
  selection = _choose_todo_task_interactively(args, context, tool_name=tool_name)
258
245
  if selection.status == "unavailable":
259
246
  raise_config_error(
260
- f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id",
247
+ f"{tool_name} requires --task-id from task list data.items[].task_id",
261
248
  fix_hint=(
262
249
  "Retry in an interactive terminal to choose from current todo tasks, "
263
- "or pass `--task-id TASK_ID` explicitly."
250
+ "or pass `--task-id` from `task list` explicitly."
264
251
  ),
265
252
  )
266
253
  if selection.status == "empty":
267
254
  raise_config_error(
268
255
  selection.message or f"{tool_name} could not open a selector because no current todo tasks are available.",
269
- fix_hint="Run `task list` to confirm current todo tasks, or retry later with `--task-id TASK_ID`.",
256
+ fix_hint="Run `task list` to confirm current todo tasks, then pass the selected `data.items[].task_id`.",
270
257
  )
271
258
  if selection.status == "cancelled":
272
259
  return cancelled_result(selection.message or "已取消")
@@ -327,7 +314,7 @@ def _choose_todo_task_interactively(
327
314
  args,
328
315
  title=title,
329
316
  unavailable_message=(
330
- f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id"
317
+ f"{tool_name} requires --task-id from task list data.items[].task_id"
331
318
  ),
332
319
  empty_message=f"{tool_name} could not open a selector because no current todo tasks are available.",
333
320
  load_options=load_options,
@@ -347,7 +334,7 @@ def _resolve_report_id_or_select(args: argparse.Namespace, context: CliContext)
347
334
  if selection.status == "empty":
348
335
  raise_config_error(
349
336
  selection.message or "task report could not open a selector because the selected task has no visible associated reports.",
350
- fix_hint="Run `task get --task-id TASK_ID` to inspect `extras.associated_reports`, or choose another task.",
337
+ fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect `extras.associated_reports`, or choose another task.",
351
338
  )
352
339
  if selection.status == "cancelled":
353
340
  return cancelled_result(selection.message or "已取消")
@@ -395,7 +382,7 @@ def _resolve_task_action_or_select(
395
382
  if selection.status == "empty":
396
383
  raise_config_error(
397
384
  selection.message or "task action could not open an action selector because no interactive actions are available.",
398
- fix_hint="Run `task get --task-id TASK_ID` to inspect available_actions, or pass a supported `--action` explicitly.",
385
+ fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect available_actions, or pass a supported `--action` explicitly.",
399
386
  )
400
387
  if selection.status == "cancelled":
401
388
  return cancelled_result(selection.message or "已取消")
@@ -476,7 +463,7 @@ def _resolve_action_payload_or_select(
476
463
  if selection.status == "empty":
477
464
  raise_config_error(
478
465
  selection.message or "task rollback could not open a selector because no rollback candidates are visible.",
479
- fix_hint="Run `task get --task-id TASK_ID` to inspect rollback candidates, or choose another action.",
466
+ fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect rollback candidates, or choose another action.",
480
467
  )
481
468
  if selection.status == "cancelled":
482
469
  return cancelled_result(selection.message or "已取消")
@@ -493,7 +480,7 @@ def _resolve_action_payload_or_select(
493
480
  if selection.status == "empty":
494
481
  raise_config_error(
495
482
  selection.message or "task transfer could not open a selector because no transfer candidates are visible.",
496
- fix_hint="Run `task get --task-id TASK_ID` to inspect transfer candidates, or choose another action.",
483
+ fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect transfer candidates, or choose another action.",
497
484
  )
498
485
  if selection.status == "cancelled":
499
486
  return cancelled_result(selection.message or "已取消")
@@ -184,12 +184,12 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
184
184
  `task_list -> task_get -> task_action_execute`
185
185
 
186
186
  - `task_list` returns task-card summaries keyed by `task_id`.
187
- - Prefer `task_get(task_id=...)` for detail reads; MCP resolves the current todo locator internally.
188
- - `task_action_execute(task_id=..., action=...)` is also supported; MCP resolves the current todo locator internally before calling the real action route.
187
+ - For detail reads and actions, pass the exact `task_id` from `task_list.data.items[].task_id`.
188
+ - `task_id` is not a row number, list index, record id, or workflow node id.
189
+ - Do not reconstruct task actions from `app_key + record_id + workflow_node_id` unless the user explicitly provides that full locator for troubleshooting.
189
190
  - `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
190
191
  - Use `task_associated_report_detail_get` for associated view or report details.
191
192
  - Use `task_workflow_log_get` for the current task context workflow log page. For full record-level data/workflow logs, first choose an accessible view with `app_get`, then call `record_logs_get(app_key, record_id, view_id)` with that same explicit `view_id`.
192
- - Task actions operate on `app_key + record_id + workflow_node_id`, not `task_id`.
193
193
 
194
194
  ## Time Handling
195
195
 
@@ -189,12 +189,12 @@ Use export only when the user explicitly asks to export/download/generate an Exc
189
189
  `task_list -> task_get -> task_action_execute`
190
190
 
191
191
  - `task_list` returns task-card summaries keyed by `task_id`.
192
- - Prefer `task_get(task_id=...)` for detail reads; MCP resolves the current todo locator internally.
193
- - `task_action_execute(task_id=..., action=...)` is also supported; MCP resolves the current todo locator internally before calling the real action route.
192
+ - For detail reads and actions, pass the exact `task_id` from `task_list.data.items[].task_id`.
193
+ - `task_id` is not a row number, list index, record id, or workflow node id.
194
+ - Do not reconstruct task actions from `app_key + record_id + workflow_node_id` unless the user explicitly provides that full locator for troubleshooting.
194
195
  - `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
195
196
  - Use `task_associated_report_detail_get` for associated view or report details.
196
197
  - Use `task_workflow_log_get` for the current task context workflow log page. For full record-level data/workflow logs, first choose an accessible view with `app_get`, then call `record_logs_get(app_key, record_id, view_id)` with that same explicit `view_id`.
197
- - Task actions operate on `app_key + record_id + workflow_node_id`, not `task_id`.
198
198
  - Treat `task_action_execute` as the tool-level action enum surface; the current task's real actions are only the ones listed in `task_get.capabilities.available_actions`.
199
199
  - Use `task_action_execute(action="save_only", fields=...)` when the user wants to save editable field changes on the current node without advancing the workflow.
200
200
  - `save_only` is exposed only when the backend current-node `editableQueIds` signal returns a non-empty result; MCP no longer infers `save_only` from local schema reconstruction.
@@ -438,6 +438,7 @@ class RecordTools(ToolBase):
438
438
  where: list[JSONObject] | None = None,
439
439
  order_by: list[JSONObject] | None = None,
440
440
  page: int = 1,
441
+ page_size: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
441
442
  view_id: str | None = None,
442
443
  output_profile: str = "normal",
443
444
  ) -> JSONObject:
@@ -450,6 +451,7 @@ class RecordTools(ToolBase):
450
451
  where=where or [],
451
452
  order_by=order_by or [],
452
453
  page=page,
454
+ page_size=page_size,
453
455
  view_id=view_id,
454
456
  list_type=None,
455
457
  view_key=None,
@@ -1881,6 +1883,7 @@ class RecordTools(ToolBase):
1881
1883
  order_by: list[JSONObject],
1882
1884
  limit: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
1883
1885
  page: int = 1,
1886
+ page_size: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
1884
1887
  view_id: str | None = None,
1885
1888
  list_type: int | None = None,
1886
1889
  view_key: str | None = None,
@@ -1899,6 +1902,8 @@ class RecordTools(ToolBase):
1899
1902
  raise_tool_error(QingflowApiError.config_error("limit must be positive"))
1900
1903
  if page <= 0:
1901
1904
  raise_tool_error(QingflowApiError.config_error("page must be positive"))
1905
+ if page_size <= 0:
1906
+ raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
1902
1907
  if not (
1903
1908
  _normalize_optional_text(view_id)
1904
1909
  or list_type is not None
@@ -1971,12 +1976,12 @@ class RecordTools(ToolBase):
1971
1976
  app_key=app_key,
1972
1977
  view_route=view_route,
1973
1978
  page_num=page,
1974
- page_size=DEFAULT_LIST_PAGE_SIZE,
1979
+ page_size=page_size,
1975
1980
  query_key=normalized_query,
1976
1981
  search_que_ids=resolved_query_fields or None,
1977
1982
  match_rules=match_rules,
1978
1983
  sort_rules=sort_rules,
1979
- max_rows=limit,
1984
+ max_rows=min(limit, page_size),
1980
1985
  selected_fields=selected_fields,
1981
1986
  output_profile="verbose" if normalized_output_profile in {"verbose", "normalized"} else DEFAULT_OUTPUT_PROFILE,
1982
1987
  )
@@ -85,33 +85,37 @@ class TaskContextTools(ToolBase):
85
85
  page_size=page_size,
86
86
  )
87
87
 
88
- @mcp.tool()
88
+ @mcp.tool(
89
+ description=(
90
+ "Read one workflow task. Prefer task_id from task_list.data.items[].task_id; "
91
+ "task_id is not a row number, list index, record id, or workflow node id."
92
+ )
93
+ )
89
94
  def task_get(
90
95
  profile: str = DEFAULT_PROFILE,
91
96
  task_id: str = "",
92
- app_key: str = "",
93
- record_id: str = "",
94
- workflow_node_id: int = 0,
95
97
  include_candidates: bool = True,
96
98
  include_associated_reports: bool = True,
97
99
  ) -> dict[str, Any]:
98
100
  return self.task_get(
99
101
  profile=profile,
100
102
  task_id=task_id,
101
- app_key=app_key,
102
- record_id=record_id,
103
- workflow_node_id=workflow_node_id,
103
+ app_key="",
104
+ record_id="",
105
+ workflow_node_id=0,
104
106
  include_candidates=include_candidates,
105
107
  include_associated_reports=include_associated_reports,
106
108
  )
107
109
 
108
- @mcp.tool(description=self._high_risk_tool_description(operation="execute", target="workflow task action"))
110
+ @mcp.tool(
111
+ description=(
112
+ self._high_risk_tool_description(operation="execute", target="workflow task action")
113
+ + " Pass task_id from task_list.data.items[].task_id. Do not pass a row number, list index, record id, or workflow node id as task_id."
114
+ )
115
+ )
109
116
  def task_action_execute(
110
117
  profile: str = DEFAULT_PROFILE,
111
118
  task_id: str = "",
112
- app_key: str = "",
113
- record_id: str = "",
114
- workflow_node_id: int = 0,
115
119
  action: str = "",
116
120
  payload: dict[str, Any] | None = None,
117
121
  fields: dict[str, Any] | None = None,
@@ -119,21 +123,23 @@ class TaskContextTools(ToolBase):
119
123
  return self.task_action_execute(
120
124
  profile=profile,
121
125
  task_id=task_id,
122
- app_key=app_key,
123
- record_id=record_id,
124
- workflow_node_id=workflow_node_id,
126
+ app_key="",
127
+ record_id="",
128
+ workflow_node_id=0,
125
129
  action=action,
126
130
  payload=payload or {},
127
131
  fields=fields or {},
128
132
  )
129
133
 
130
- @mcp.tool()
134
+ @mcp.tool(
135
+ description=(
136
+ "Read a task-associated report. Pass task_id from task_list.data.items[].task_id; "
137
+ "task_id is not a row number, list index, record id, or workflow node id."
138
+ )
139
+ )
131
140
  def task_associated_report_detail_get(
132
141
  profile: str = DEFAULT_PROFILE,
133
142
  task_id: str = "",
134
- app_key: str = "",
135
- record_id: str = "",
136
- workflow_node_id: int = 0,
137
143
  report_id: int = 0,
138
144
  page: int = 1,
139
145
  page_size: int = 20,
@@ -141,28 +147,30 @@ class TaskContextTools(ToolBase):
141
147
  return self.task_associated_report_detail_get(
142
148
  profile=profile,
143
149
  task_id=task_id,
144
- app_key=app_key,
145
- record_id=record_id,
146
- workflow_node_id=workflow_node_id,
150
+ app_key="",
151
+ record_id="",
152
+ workflow_node_id=0,
147
153
  report_id=report_id,
148
154
  page=page,
149
155
  page_size=page_size,
150
156
  )
151
157
 
152
- @mcp.tool()
158
+ @mcp.tool(
159
+ description=(
160
+ "Read workflow log for one task context. Pass task_id from task_list.data.items[].task_id; "
161
+ "task_id is not a row number, list index, record id, or workflow node id."
162
+ )
163
+ )
153
164
  def task_workflow_log_get(
154
165
  profile: str = DEFAULT_PROFILE,
155
166
  task_id: str = "",
156
- app_key: str = "",
157
- record_id: str = "",
158
- workflow_node_id: int = 0,
159
167
  ) -> dict[str, Any]:
160
168
  return self.task_workflow_log_get(
161
169
  profile=profile,
162
170
  task_id=task_id,
163
- app_key=app_key,
164
- record_id=record_id,
165
- workflow_node_id=workflow_node_id,
171
+ app_key="",
172
+ record_id="",
173
+ workflow_node_id=0,
166
174
  )
167
175
 
168
176
  @tool_cn_name("任务上下文列表")
@@ -1302,14 +1310,14 @@ class TaskContextTools(ToolBase):
1302
1310
  searched = ", ".join(incomplete_task_boxes)
1303
1311
  raise_tool_error(
1304
1312
  QingflowApiError.config_error(
1305
- f"task_id={task_id_text} resolved to an incomplete task locator in task_box={searched}; please refresh the task list and retry"
1313
+ f"task_id={task_id_text} resolved to an incomplete task locator in task_box={searched}; rerun task_list and pass the exact data.items[].task_id. Do not substitute a row number or rebuild the locator from app_key/record_id/workflow_node_id."
1306
1314
  )
1307
1315
  )
1308
1316
  if inaccessible_task_boxes:
1309
1317
  searched = ", ".join(str(item.get("task_box")) for item in inaccessible_task_boxes)
1310
1318
  raise_tool_error(
1311
1319
  QingflowApiError.config_error(
1312
- f"task_id={task_id_text} was not found in visible task boxes; some task boxes were not searchable in the current permission context: {searched}",
1320
+ f"task_id={task_id_text} was not found in visible task boxes; some task boxes were not searchable in the current permission context: {searched}. Rerun task_list and use data.items[].task_id; do not substitute a row number or rebuild the locator.",
1313
1321
  details={
1314
1322
  "task_id": task_id_text,
1315
1323
  "searched_task_boxes": list(searched_task_boxes),
@@ -1319,7 +1327,7 @@ class TaskContextTools(ToolBase):
1319
1327
  )
1320
1328
  raise_tool_error(
1321
1329
  QingflowApiError.config_error(
1322
- f"task_id={task_id_text} was not found in the current visible task boxes (todo, initiated, cc, done)"
1330
+ f"task_id={task_id_text} was not found in the current visible task boxes (todo, initiated, cc, done). Rerun task_list and pass data.items[].task_id; do not use the displayed row number or guess app_key/record_id/workflow_node_id."
1323
1331
  )
1324
1332
  )
1325
1333