@qingflow-tech/qingflow-app-builder-mcp 1.0.31 → 1.0.33
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/cli/commands/task.py +0 -6
- package/src/qingflow_mcp/cli/main.py +43 -0
- package/src/qingflow_mcp/server.py +1 -1
- package/src/qingflow_mcp/server_app_user.py +1 -1
- package/src/qingflow_mcp/tools/approval_tools.py +1 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +31 -12
- package/src/qingflow_mcp/version.py +40 -0
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.33
|
|
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.33 qingflow-app-builder-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -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={},
|
|
@@ -9,6 +9,7 @@ from ..errors import QingflowApiError, backend_code_value_int, message_looks_lik
|
|
|
9
9
|
from ..public_surface import cli_public_tool_spec_from_namespace
|
|
10
10
|
from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
|
|
11
11
|
from ..tools.ai_builder_tools import _attach_builder_apply_envelope
|
|
12
|
+
from ..version import get_cli_version
|
|
12
13
|
from .context import CliContext, build_cli_context
|
|
13
14
|
from .formatters import emit_json_result, emit_text_result
|
|
14
15
|
from .commands import register_all_commands
|
|
@@ -34,7 +35,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
34
35
|
parser = _QingflowArgumentParser(prog="qingflow", description="Qingflow CLI")
|
|
35
36
|
parser.add_argument("--profile", default="default", help="会话 profile,默认 default")
|
|
36
37
|
parser.add_argument("--json", action="store_true", help="输出 JSON")
|
|
38
|
+
parser.add_argument("--version", action="store_true", help="输出 Qingflow CLI 版本")
|
|
37
39
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
40
|
+
version_parser = subparsers.add_parser("version", help="输出 Qingflow CLI 版本")
|
|
41
|
+
version_parser.set_defaults(handler=_handle_version, format_hint="version")
|
|
38
42
|
register_all_commands(subparsers)
|
|
39
43
|
return parser
|
|
40
44
|
|
|
@@ -54,6 +58,8 @@ def run(
|
|
|
54
58
|
err = stderr or sys.stderr
|
|
55
59
|
parser = build_parser()
|
|
56
60
|
normalized_argv = _normalize_global_args(list(argv) if argv is not None else sys.argv[1:])
|
|
61
|
+
if "--version" in normalized_argv:
|
|
62
|
+
return _emit_version(json_mode=_should_force_json_output_argv_for_version(normalized_argv), stdout=out)
|
|
57
63
|
try:
|
|
58
64
|
args = parser.parse_args(normalized_argv)
|
|
59
65
|
except _CliArgumentError as exc:
|
|
@@ -76,6 +82,8 @@ def run(
|
|
|
76
82
|
setattr(args, "_stdin", sys.stdin)
|
|
77
83
|
setattr(args, "_stdout_stream", out)
|
|
78
84
|
setattr(args, "_stderr_stream", err)
|
|
85
|
+
if getattr(args, "command", "") == "version":
|
|
86
|
+
return _emit_version(json_mode=bool(args.json), stdout=out)
|
|
79
87
|
handler = getattr(args, "handler", None)
|
|
80
88
|
if handler is None:
|
|
81
89
|
parser.print_help(out)
|
|
@@ -111,6 +119,37 @@ def run(
|
|
|
111
119
|
return exit_code
|
|
112
120
|
|
|
113
121
|
|
|
122
|
+
def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
|
|
123
|
+
version = get_cli_version()
|
|
124
|
+
return {
|
|
125
|
+
"ok": True,
|
|
126
|
+
"status": "success",
|
|
127
|
+
"version": version,
|
|
128
|
+
"package": "@qingflow-tech/qingflow-cli",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _emit_version(*, json_mode: bool, stdout: TextIO) -> int:
|
|
133
|
+
version = get_cli_version()
|
|
134
|
+
if json_mode:
|
|
135
|
+
emit_json_result(
|
|
136
|
+
{
|
|
137
|
+
"ok": True,
|
|
138
|
+
"status": "success",
|
|
139
|
+
"version": version,
|
|
140
|
+
"package": "@qingflow-tech/qingflow-cli",
|
|
141
|
+
},
|
|
142
|
+
stream=stdout,
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
stdout.write(f"{version}\n")
|
|
146
|
+
return 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _should_force_json_output_argv_for_version(argv: list[str]) -> bool:
|
|
150
|
+
return "--json" in argv
|
|
151
|
+
|
|
152
|
+
|
|
114
153
|
def _normalize_global_args(argv: list[str]) -> list[str]:
|
|
115
154
|
global_args: list[str] = []
|
|
116
155
|
remaining: list[str] = []
|
|
@@ -121,6 +160,10 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
|
|
|
121
160
|
global_args.append(token)
|
|
122
161
|
index += 1
|
|
123
162
|
continue
|
|
163
|
+
if token == "--version":
|
|
164
|
+
global_args.append(token)
|
|
165
|
+
index += 1
|
|
166
|
+
continue
|
|
124
167
|
if token == "--profile":
|
|
125
168
|
global_args.append(token)
|
|
126
169
|
if index + 1 >= len(argv):
|
|
@@ -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`.
|
|
@@ -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 []
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from importlib import metadata
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_cli_version() -> str:
|
|
9
|
+
package_json_version = _find_package_json_version()
|
|
10
|
+
if package_json_version:
|
|
11
|
+
return package_json_version
|
|
12
|
+
try:
|
|
13
|
+
return metadata.version("qingflow-mcp")
|
|
14
|
+
except metadata.PackageNotFoundError:
|
|
15
|
+
return "0+local"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _find_package_json_version() -> str | None:
|
|
19
|
+
current = Path(__file__).resolve()
|
|
20
|
+
for parent in current.parents:
|
|
21
|
+
package_json = parent / "package.json"
|
|
22
|
+
if not package_json.exists():
|
|
23
|
+
continue
|
|
24
|
+
try:
|
|
25
|
+
payload = json.loads(package_json.read_text(encoding="utf-8"))
|
|
26
|
+
except (OSError, json.JSONDecodeError):
|
|
27
|
+
continue
|
|
28
|
+
name = str(payload.get("name") or "")
|
|
29
|
+
version = str(payload.get("version") or "")
|
|
30
|
+
if version and name in {
|
|
31
|
+
"qingflow-mcp-workspace",
|
|
32
|
+
"@qingflow-tech/qingflow-cli",
|
|
33
|
+
"@qingflow-tech/qingflow-app-user-mcp",
|
|
34
|
+
"@qingflow-tech/qingflow-app-builder-mcp",
|
|
35
|
+
"@josephyan/qingflow-cli",
|
|
36
|
+
"@josephyan/qingflow-app-user-mcp",
|
|
37
|
+
"@josephyan/qingflow-app-builder-mcp",
|
|
38
|
+
}:
|
|
39
|
+
return version
|
|
40
|
+
return None
|