@qingflow-tech/qingflow-app-user-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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @qingflow-tech/qingflow-app-user-mcp@1.0.31
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.33
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.31 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.33 qingflow-app-user-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-user-mcp",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory 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.31"
7
+ version = "1.0.33"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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
- - Do not reconstruct task actions from `app_key + record_id + workflow_node_id` unless the user explicitly provides that full locator for troubleshooting.
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
- - Do not reconstruct task actions from `app_key + record_id + workflow_node_id` unless the user explicitly provides that full locator for troubleshooting.
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).task_action_execute(
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
- app_key: str,
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
- app_key=app_key,
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 app_key='{resolved_app_key}' record_id={record_id_text} workflow_node_id={resolved_workflow_node_id}"
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