@qingflow-tech/qingflow-app-builder-mcp 1.0.30 → 1.0.32
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/builder_facade/service.py +38 -0
- package/src/qingflow_mcp/cli/commands/task.py +0 -6
- package/src/qingflow_mcp/server.py +1 -1
- package/src/qingflow_mcp/server_app_user.py +1 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1 -0
- package/src/qingflow_mcp/tools/approval_tools.py +1 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +31 -12
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.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.32
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.32 qingflow-app-builder-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -99,6 +99,8 @@ from .models import (
|
|
|
99
99
|
FlowNodePermissionsPatch,
|
|
100
100
|
)
|
|
101
101
|
|
|
102
|
+
BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT = 50
|
|
103
|
+
|
|
102
104
|
|
|
103
105
|
QUESTION_TYPE_TO_FIELD_TYPE: dict[int, str] = {
|
|
104
106
|
2: FieldType.text.value,
|
|
@@ -5998,6 +6000,42 @@ class AiBuilderFacade:
|
|
|
5998
6000
|
warnings: list[dict[str, Any]] = []
|
|
5999
6001
|
items: list[dict[str, Any]] = []
|
|
6000
6002
|
permission_verified = True
|
|
6003
|
+
if isinstance(raw_items, list) and len(raw_items) > BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT:
|
|
6004
|
+
normalized = _normalize_portal_list_items(raw_items)
|
|
6005
|
+
response = PortalListResponse(items=normalized, total=len(normalized))
|
|
6006
|
+
warnings.append(
|
|
6007
|
+
_warning(
|
|
6008
|
+
"PORTAL_PERMISSION_VERIFICATION_SKIPPED",
|
|
6009
|
+
"builder portal_list returned the discovery list without per-portal edit permission checks because the workspace has too many portals",
|
|
6010
|
+
total_available=len(raw_items),
|
|
6011
|
+
detail_verify_limit=BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT,
|
|
6012
|
+
)
|
|
6013
|
+
)
|
|
6014
|
+
return {
|
|
6015
|
+
"status": "success",
|
|
6016
|
+
"error_code": None,
|
|
6017
|
+
"recoverable": False,
|
|
6018
|
+
"message": "list builder portal discovery items",
|
|
6019
|
+
"normalized_args": {},
|
|
6020
|
+
"missing_fields": [],
|
|
6021
|
+
"allowed_values": {},
|
|
6022
|
+
"details": {
|
|
6023
|
+
"total_available": len(raw_items),
|
|
6024
|
+
"detail_verify_limit": BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT,
|
|
6025
|
+
"permission_filter_applied": False,
|
|
6026
|
+
},
|
|
6027
|
+
"request_id": None,
|
|
6028
|
+
"suggested_next_call": None,
|
|
6029
|
+
"noop": False,
|
|
6030
|
+
"warnings": warnings,
|
|
6031
|
+
"verification": {
|
|
6032
|
+
"portal_list_loaded": True,
|
|
6033
|
+
"portal_permissions_verified": False,
|
|
6034
|
+
"permission_filter_applied": False,
|
|
6035
|
+
},
|
|
6036
|
+
"verified": False,
|
|
6037
|
+
**response.model_dump(mode="json"),
|
|
6038
|
+
}
|
|
6001
6039
|
for raw_item in raw_items if isinstance(raw_items, list) else []:
|
|
6002
6040
|
if not isinstance(raw_item, dict):
|
|
6003
6041
|
continue
|
|
@@ -162,9 +162,6 @@ def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
162
162
|
return context.task.task_action_execute(
|
|
163
163
|
profile=args.profile,
|
|
164
164
|
task_id=args.task_id,
|
|
165
|
-
app_key=args.app_key or "",
|
|
166
|
-
record_id=args.record_id or "",
|
|
167
|
-
workflow_node_id=int(args.workflow_node_id or 0),
|
|
168
165
|
action=args.action,
|
|
169
166
|
payload=payload,
|
|
170
167
|
fields=fields,
|
|
@@ -225,9 +222,6 @@ def _run_task_workbench_task_loop(args: argparse.Namespace, context: CliContext)
|
|
|
225
222
|
result = context.task.task_action_execute(
|
|
226
223
|
profile=args.profile,
|
|
227
224
|
task_id=args.task_id,
|
|
228
|
-
app_key=args.app_key or "",
|
|
229
|
-
record_id=args.record_id or "",
|
|
230
|
-
workflow_node_id=int(args.workflow_node_id or 0),
|
|
231
225
|
action=args.action,
|
|
232
226
|
payload=payload_selection,
|
|
233
227
|
fields={},
|
|
@@ -186,7 +186,7 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
186
186
|
- `task_list` returns task-card summaries keyed by `task_id`.
|
|
187
187
|
- For detail reads and actions, pass the exact `task_id` from `task_list.data.items[].task_id`.
|
|
188
188
|
- `task_id` is not a row number, list index, record id, or workflow node id.
|
|
189
|
-
-
|
|
189
|
+
- `task_action_execute` only uses `task_id`; do not reconstruct or pass `app_key + record_id + workflow_node_id` for actions.
|
|
190
190
|
- `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
|
|
191
191
|
- Use `task_associated_report_detail_get` for associated view or report details.
|
|
192
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`.
|
|
@@ -191,7 +191,7 @@ Use export only when the user explicitly asks to export/download/generate an Exc
|
|
|
191
191
|
- `task_list` returns task-card summaries keyed by `task_id`.
|
|
192
192
|
- For detail reads and actions, pass the exact `task_id` from `task_list.data.items[].task_id`.
|
|
193
193
|
- `task_id` is not a row number, list index, record id, or workflow node id.
|
|
194
|
-
-
|
|
194
|
+
- `task_action_execute` only uses `task_id`; do not reconstruct or pass `app_key + record_id + workflow_node_id` for actions.
|
|
195
195
|
- `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
|
|
196
196
|
- Use `task_associated_report_detail_get` for associated view or report details.
|
|
197
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`.
|
|
@@ -5343,6 +5343,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5343
5343
|
"returns builder-configurable portal list items only",
|
|
5344
5344
|
"use this as the builder portal discovery path before portal_get",
|
|
5345
5345
|
"results are compact list items, not raw dash payloads",
|
|
5346
|
+
"large workspaces may return an unfiltered discovery list with portal_permissions_verified=false instead of probing every portal detail",
|
|
5346
5347
|
],
|
|
5347
5348
|
"minimal_example": {
|
|
5348
5349
|
"profile": "default",
|
|
@@ -1055,7 +1055,7 @@ class ApprovalTools(ToolBase):
|
|
|
1055
1055
|
if node_id is None:
|
|
1056
1056
|
node_payload = dict(payload or {})
|
|
1057
1057
|
node_id = self._extract_node_id(node_payload)
|
|
1058
|
-
delegated = TaskContextTools(self.sessions, self.backend).
|
|
1058
|
+
delegated = TaskContextTools(self.sessions, self.backend)._task_action_execute_with_locator(
|
|
1059
1059
|
profile=profile,
|
|
1060
1060
|
app_key=app_key,
|
|
1061
1061
|
record_id=record_id,
|
|
@@ -126,9 +126,6 @@ class TaskContextTools(ToolBase):
|
|
|
126
126
|
return self.task_action_execute(
|
|
127
127
|
profile=profile,
|
|
128
128
|
task_id=task_id,
|
|
129
|
-
app_key="",
|
|
130
|
-
record_id="",
|
|
131
|
-
workflow_node_id=0,
|
|
132
129
|
action=action,
|
|
133
130
|
payload=payload or {},
|
|
134
131
|
fields=fields or {},
|
|
@@ -317,9 +314,7 @@ class TaskContextTools(ToolBase):
|
|
|
317
314
|
self,
|
|
318
315
|
*,
|
|
319
316
|
profile: str,
|
|
320
|
-
|
|
321
|
-
record_id: Any,
|
|
322
|
-
workflow_node_id: int,
|
|
317
|
+
task_id: Any,
|
|
323
318
|
fields: dict[str, Any] | None = None,
|
|
324
319
|
) -> dict[str, Any]:
|
|
325
320
|
"""执行任务相关逻辑。"""
|
|
@@ -328,9 +323,7 @@ class TaskContextTools(ToolBase):
|
|
|
328
323
|
raise_tool_error(QingflowApiError.config_error("fields is required and must be non-empty for task_save_only"))
|
|
329
324
|
return self.task_action_execute(
|
|
330
325
|
profile=profile,
|
|
331
|
-
|
|
332
|
-
record_id=record_id,
|
|
333
|
-
workflow_node_id=workflow_node_id,
|
|
326
|
+
task_id=task_id,
|
|
334
327
|
action="save_only",
|
|
335
328
|
payload={},
|
|
336
329
|
fields=field_updates,
|
|
@@ -338,6 +331,34 @@ class TaskContextTools(ToolBase):
|
|
|
338
331
|
|
|
339
332
|
@tool_cn_name("执行任务动作")
|
|
340
333
|
def task_action_execute(
|
|
334
|
+
self,
|
|
335
|
+
*,
|
|
336
|
+
profile: str,
|
|
337
|
+
task_id: Any,
|
|
338
|
+
action: str,
|
|
339
|
+
payload: dict[str, Any],
|
|
340
|
+
fields: dict[str, Any] | None = None,
|
|
341
|
+
) -> dict[str, Any]:
|
|
342
|
+
"""执行任务相关逻辑。"""
|
|
343
|
+
if task_id in (None, ""):
|
|
344
|
+
raise_tool_error(
|
|
345
|
+
QingflowApiError.config_error(
|
|
346
|
+
"task_id is required for task_action_execute; get it from task_list.data.items[].task_id"
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return self._task_action_execute_with_locator(
|
|
351
|
+
profile=profile,
|
|
352
|
+
task_id=task_id,
|
|
353
|
+
app_key="",
|
|
354
|
+
record_id="",
|
|
355
|
+
workflow_node_id=0,
|
|
356
|
+
action=action,
|
|
357
|
+
payload=payload,
|
|
358
|
+
fields=fields,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def _task_action_execute_with_locator(
|
|
341
362
|
self,
|
|
342
363
|
*,
|
|
343
364
|
profile: str,
|
|
@@ -349,10 +370,8 @@ class TaskContextTools(ToolBase):
|
|
|
349
370
|
payload: dict[str, Any],
|
|
350
371
|
fields: dict[str, Any] | None = None,
|
|
351
372
|
) -> dict[str, Any]:
|
|
352
|
-
"""执行任务相关逻辑。"""
|
|
353
373
|
if task_id in (None, ""):
|
|
354
374
|
normalize_positive_id_int(record_id, field_name="record_id")
|
|
355
|
-
|
|
356
375
|
normalized_action = (action or "").strip().lower()
|
|
357
376
|
if normalized_action not in {"approve", "reject", "rollback", "transfer", "urge", "save_only"}:
|
|
358
377
|
raise_tool_error(
|
|
@@ -492,7 +511,7 @@ class TaskContextTools(ToolBase):
|
|
|
492
511
|
raise_tool_error(QingflowApiError.config_error(message))
|
|
493
512
|
raise_tool_error(
|
|
494
513
|
QingflowApiError.config_error(
|
|
495
|
-
f"task action '{normalized_action}' is not currently available for
|
|
514
|
+
f"task action '{normalized_action}' is not currently available for task_id='{task_id_text}'"
|
|
496
515
|
)
|
|
497
516
|
)
|
|
498
517
|
feedback_required_for = capabilities.get("action_constraints", {}).get("feedback_required_for") or []
|