@josephyan/qingflow-app-builder-mcp 1.1.25 → 1.1.26

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.
Files changed (98) hide show
  1. package/README.md +5 -6
  2. package/docs/local-agent-install.md +6 -57
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
  5. package/npm/lib/runtime.mjs +101 -21
  6. package/npm/scripts/postinstall.mjs +10 -1
  7. package/package.json +2 -3
  8. package/pyproject.toml +1 -1
  9. package/skills/qingflow-app-builder/SKILL.md +42 -104
  10. package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
  11. package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
  12. package/skills/qingflow-app-builder/references/create-app.md +11 -30
  13. package/skills/qingflow-app-builder/references/environments.md +1 -1
  14. package/skills/qingflow-app-builder/references/gotchas.md +9 -31
  15. package/skills/qingflow-app-builder/references/match-rules.md +0 -15
  16. package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
  17. package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
  18. package/skills/qingflow-app-builder/references/update-flow.md +38 -22
  19. package/skills/qingflow-app-builder/references/update-schema.md +0 -14
  20. package/skills/qingflow-app-builder/references/update-views.md +14 -83
  21. package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
  22. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
  23. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
  24. package/skills/qingflow-mcp-setup/SKILL.md +111 -0
  25. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  26. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  27. package/skills/qingflow-mcp-setup/references/environments.md +61 -0
  28. package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
  29. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  30. package/skills/qingflow-workflow-builder/SKILL.md +96 -0
  31. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  32. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  33. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  34. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  35. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  36. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  37. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  38. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  39. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  40. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  41. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  42. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  43. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  44. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  45. package/src/qingflow_mcp/__init__.py +1 -1
  46. package/src/qingflow_mcp/__main__.py +2 -6
  47. package/src/qingflow_mcp/builder_facade/models.py +108 -485
  48. package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
  49. package/src/qingflow_mcp/cli/commands/builder.py +364 -322
  50. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  51. package/src/qingflow_mcp/cli/commands/common.py +3 -12
  52. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  53. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  54. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  55. package/src/qingflow_mcp/cli/commands/record.py +27 -101
  56. package/src/qingflow_mcp/cli/commands/task.py +47 -28
  57. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  58. package/src/qingflow_mcp/cli/context.py +3 -0
  59. package/src/qingflow_mcp/cli/formatters.py +16 -784
  60. package/src/qingflow_mcp/cli/main.py +33 -117
  61. package/src/qingflow_mcp/errors.py +2 -43
  62. package/src/qingflow_mcp/public_surface.py +17 -26
  63. package/src/qingflow_mcp/response_trim.py +17 -81
  64. package/src/qingflow_mcp/server.py +12 -14
  65. package/src/qingflow_mcp/server_app_builder.py +36 -79
  66. package/src/qingflow_mcp/server_app_user.py +16 -22
  67. package/src/qingflow_mcp/session_store.py +7 -11
  68. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  69. package/src/qingflow_mcp/solution/executor.py +18 -245
  70. package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3103
  71. package/src/qingflow_mcp/tools/app_tools.py +43 -184
  72. package/src/qingflow_mcp/tools/approval_tools.py +35 -197
  73. package/src/qingflow_mcp/tools/auth_tools.py +16 -92
  74. package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
  75. package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
  76. package/src/qingflow_mcp/tools/directory_tools.py +72 -236
  77. package/src/qingflow_mcp/tools/export_tools.py +34 -244
  78. package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
  79. package/src/qingflow_mcp/tools/file_tools.py +3 -9
  80. package/src/qingflow_mcp/tools/import_tools.py +49 -336
  81. package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
  82. package/src/qingflow_mcp/tools/package_tools.py +6 -118
  83. package/src/qingflow_mcp/tools/portal_tools.py +3 -39
  84. package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
  85. package/src/qingflow_mcp/tools/record_tools.py +360 -1145
  86. package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
  87. package/src/qingflow_mcp/tools/role_tools.py +9 -80
  88. package/src/qingflow_mcp/tools/solution_tools.py +45 -59
  89. package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
  90. package/src/qingflow_mcp/tools/task_tools.py +29 -113
  91. package/src/qingflow_mcp/tools/view_tools.py +3 -106
  92. package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
  93. package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
  94. package/npm/bin/qingflow-skills.mjs +0 -5
  95. package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
  96. package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
  97. package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
  98. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -1,12 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any
5
4
 
6
5
  from mcp.server.fastmcp import FastMCP
7
6
 
8
7
  from ..config import DEFAULT_PROFILE
9
- from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token, raise_tool_error
8
+ from ..errors import QingflowApiError, raise_tool_error
10
9
  from ..list_type_labels import get_record_list_type_label, get_task_type_label
11
10
  from .base import ToolBase, tool_cn_name
12
11
 
@@ -151,17 +150,7 @@ class TaskTools(ToolBase):
151
150
  app_key: str | None,
152
151
  ) -> dict[str, Any]:
153
152
  """执行任务相关逻辑。"""
154
- try:
155
- raw = self.task_statistics(profile=profile, app_key=app_key)
156
- except RuntimeError as exc:
157
- if not _is_optional_task_runtime_error(exc):
158
- raise
159
- return self._runtime_error_as_auxiliary_result(
160
- exc,
161
- error_code="TASK_SUMMARY_UNAVAILABLE",
162
- selection={"app_key": app_key},
163
- fallback_hint="Use task list/get directly; task summary is an auxiliary statistics entrypoint.",
164
- )
153
+ raw = self.task_statistics(profile=profile, app_key=app_key)
165
154
  statistics = raw.get("statistics", {})
166
155
  summary = self._normalize_task_summary_payload(statistics)
167
156
  return {
@@ -254,51 +243,34 @@ class TaskTools(ToolBase):
254
243
  if limit <= 0:
255
244
  raise_tool_error(QingflowApiError.config_error("limit must be positive"))
256
245
 
257
- try:
258
- if dimension == "worksheet":
259
- raw = self.task_worksheet_statistics(
260
- profile=profile,
261
- type=normalized_type,
262
- worksheet_name=query,
263
- page_num=1,
264
- page_size=max(limit, 20),
265
- )
266
- source = raw.get("page", {})
267
- elif app_key:
268
- raw = self.task_node_statistics(
269
- profile=profile,
270
- app_key=app_key,
271
- type=normalized_type,
272
- search_key=query,
273
- )
274
- source = raw.get("nodes", {})
275
- else:
276
- raw = self.task_workflow_nodes(
277
- profile=profile,
278
- type=normalized_type,
279
- status=str(normalized_status),
280
- app_key_list=None,
281
- search_key=query,
282
- page_num=1,
283
- page_size=max(limit, 20),
284
- )
285
- source = raw.get("page", {})
286
- except RuntimeError as exc:
287
- if not _is_optional_task_runtime_error(exc):
288
- raise
289
- return self._runtime_error_as_auxiliary_result(
290
- exc,
291
- error_code="TASK_FACETS_UNAVAILABLE",
292
- selection={
293
- "task_box": task_box,
294
- "flow_status": flow_status,
295
- "dimension": dimension,
296
- "app_key": app_key,
297
- "query": query,
298
- "limit": limit,
299
- },
300
- fallback_hint="Use task list/get directly; task facets are an auxiliary grouping entrypoint.",
246
+ if dimension == "worksheet":
247
+ raw = self.task_worksheet_statistics(
248
+ profile=profile,
249
+ type=normalized_type,
250
+ worksheet_name=query,
251
+ page_num=1,
252
+ page_size=max(limit, 20),
301
253
  )
254
+ source = raw.get("page", {})
255
+ elif app_key:
256
+ raw = self.task_node_statistics(
257
+ profile=profile,
258
+ app_key=app_key,
259
+ type=normalized_type,
260
+ search_key=query,
261
+ )
262
+ source = raw.get("nodes", {})
263
+ else:
264
+ raw = self.task_workflow_nodes(
265
+ profile=profile,
266
+ type=normalized_type,
267
+ status=str(normalized_status),
268
+ app_key_list=None,
269
+ search_key=query,
270
+ page_num=1,
271
+ page_size=max(limit, 20),
272
+ )
273
+ source = raw.get("page", {})
302
274
 
303
275
  groups = self._normalize_task_facets(source)
304
276
  rows_truncated = len(groups) > limit
@@ -852,46 +824,6 @@ class TaskTools(ToolBase):
852
824
  groups.append({"key": key if key is not None else label, "label": label, "count": count})
853
825
  return groups
854
826
 
855
- def _runtime_error_as_auxiliary_result(
856
- self,
857
- error: RuntimeError,
858
- *,
859
- error_code: str,
860
- selection: dict[str, Any],
861
- fallback_hint: str,
862
- ) -> dict[str, Any]:
863
- """Return a structured failure for optional task discovery helpers."""
864
- try:
865
- payload = json.loads(str(error))
866
- except json.JSONDecodeError:
867
- payload = {"message": str(error)}
868
- details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
869
- warning: dict[str, Any] = {
870
- "code": error_code,
871
- "message": fallback_hint,
872
- }
873
- for key in ("category", "backend_code", "request_id", "http_status"):
874
- if payload.get(key) is not None:
875
- warning[key] = payload.get(key)
876
- response: dict[str, Any] = {
877
- "ok": False,
878
- "status": "failed",
879
- "error_code": details.get("error_code") or error_code,
880
- "message": payload.get("message") or str(error),
881
- "warnings": [warning],
882
- "output_profile": "normal",
883
- "data": {
884
- "selection": selection,
885
- "fallback_hint": fallback_hint,
886
- },
887
- }
888
- for key in ("category", "backend_code", "request_id", "http_status"):
889
- if payload.get(key) is not None:
890
- response[key] = payload.get(key)
891
- if details:
892
- response["details"] = details
893
- return response
894
-
895
827
  def _public_task_action_response(
896
828
  self,
897
829
  raw: dict[str, Any],
@@ -927,22 +859,6 @@ class TaskTools(ToolBase):
927
859
  }
928
860
 
929
861
 
930
- def _is_optional_task_runtime_error(error: RuntimeError) -> bool:
931
- try:
932
- payload = json.loads(str(error))
933
- except json.JSONDecodeError:
934
- return False
935
- if not isinstance(payload, dict):
936
- return False
937
- if str(payload.get("category") or "").strip().lower() == "auth":
938
- return False
939
- if message_looks_like_invalid_token(payload.get("message")):
940
- return False
941
- if backend_code_value_int(payload.get("http_status")) == 401:
942
- return False
943
- return backend_code_value_int(payload.get("backend_code")) in {40002, 40027, 404} or backend_code_value_int(payload.get("http_status")) == 404
944
-
945
-
946
862
  def _task_page_items(payload: Any) -> list[Any]:
947
863
  if isinstance(payload, list):
948
864
  return payload
@@ -3,9 +3,8 @@ from __future__ import annotations
3
3
  from mcp.server.fastmcp import FastMCP
4
4
 
5
5
  from ..config import DEFAULT_PROFILE
6
- from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
6
+ from ..errors import QingflowApiError, raise_tool_error
7
7
  from ..json_types import JSONObject, JSONValue
8
- from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS
9
8
  from .base import ToolBase, tool_cn_name
10
9
 
11
10
 
@@ -61,31 +60,7 @@ class ViewTools(ToolBase):
61
60
  def view_list(self, *, profile: str, app_key: str) -> JSONObject:
62
61
  """执行视图相关逻辑。"""
63
62
  self._require_app_key(app_key)
64
-
65
- def runner(session_profile, context):
66
- warnings: list[JSONObject] = []
67
- verification: JSONObject = {"custom_views_loaded": True, "system_view_fallback_used": False}
68
- try:
69
- result = self.backend.request("GET", context, f"/app/{app_key}/view/viewList")
70
- return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
71
- except QingflowApiError as exc:
72
- if not _is_optional_view_list_error(exc):
73
- raise
74
- items, probe_warnings = self._resolve_accessible_system_views(context, app_key)
75
- warnings.extend(probe_warnings)
76
- warnings.append(_view_list_fallback_warning("VIEW_LIST_UNAVAILABLE", exc))
77
- verification = {"custom_views_loaded": False, "system_view_fallback_used": True}
78
- return {
79
- "profile": profile,
80
- "ws_id": session_profile.selected_ws_id,
81
- "app_key": app_key,
82
- "result": items,
83
- "items": items,
84
- "warnings": warnings,
85
- "verification": verification,
86
- }
87
-
88
- return self._run(profile, runner)
63
+ return self._request(profile, "GET", f"/app/{app_key}/view/viewList", app_key=app_key)
89
64
 
90
65
  @tool_cn_name("视图列表(扁平)")
91
66
  def view_list_flat(self, *, profile: str, app_key: str) -> JSONObject:
@@ -132,40 +107,7 @@ class ViewTools(ToolBase):
132
107
  """执行视图相关逻辑。"""
133
108
  self._require_viewgraph_key(viewgraph_key)
134
109
  params = {"pass": passcode} if passcode else None
135
- def runner(session_profile, context):
136
- warnings: list[JSONObject] = []
137
- verification: JSONObject = {"base_info_verified": True, "fallback_config_used": False}
138
- try:
139
- result = self.backend.request("GET", context, f"/view/{viewgraph_key}/viewConfig/baseInfo", params=params)
140
- except QingflowApiError as error:
141
- if not _is_optional_view_list_error(error):
142
- raise
143
- result = self.backend.request("GET", context, f"/view/{viewgraph_key}/viewConfig")
144
- verification["base_info_verified"] = False
145
- verification["fallback_config_used"] = True
146
- warning: JSONObject = {
147
- "code": "VIEW_BASE_INFO_UNAVAILABLE",
148
- "message": "view_get_base_info used viewConfig because view baseInfo is unavailable in this permission context.",
149
- }
150
- if error.backend_code is not None:
151
- warning["backend_code"] = error.backend_code
152
- if error.http_status is not None:
153
- warning["http_status"] = error.http_status
154
- if error.request_id is not None:
155
- warning["request_id"] = error.request_id
156
- if error.details:
157
- warning["details"] = error.details
158
- warnings.append(warning)
159
- return {
160
- "profile": profile,
161
- "ws_id": session_profile.selected_ws_id,
162
- "viewgraph_key": viewgraph_key,
163
- "result": result,
164
- "warnings": warnings,
165
- "verification": verification,
166
- }
167
-
168
- return self._run(profile, runner)
110
+ return self._request(profile, "GET", f"/view/{viewgraph_key}/viewConfig/baseInfo", viewgraph_key=viewgraph_key, params=params)
169
111
 
170
112
  @tool_cn_name("更新视图配置")
171
113
  def view_update(self, *, profile: str, viewgraph_key: str, payload: JSONObject) -> JSONObject:
@@ -361,34 +303,6 @@ class ViewTools(ToolBase):
361
303
  if value <= 0:
362
304
  raise_tool_error(QingflowApiError.config_error(f"{field_name} must be positive"))
363
305
 
364
- def _resolve_accessible_system_views(self, context, app_key: str) -> tuple[list[JSONObject], list[JSONObject]]:
365
- items: list[JSONObject] = []
366
- warnings: list[JSONObject] = []
367
- for view_id, list_type, name in SYSTEM_VIEW_DEFINITIONS:
368
- try:
369
- self.backend.request(
370
- "POST",
371
- context,
372
- f"/app/{app_key}/apply/filter",
373
- json_body={"type": list_type, "pageNum": 1, "pageSize": 1},
374
- )
375
- except QingflowApiError as exc:
376
- if _is_optional_view_list_error(exc):
377
- continue
378
- raise
379
- items.append(
380
- {
381
- "view_id": view_id,
382
- "viewId": view_id,
383
- "viewName": name,
384
- "name": name,
385
- "kind": "system",
386
- "list_type": list_type,
387
- "analysis_supported": True,
388
- }
389
- )
390
- return items, warnings
391
-
392
306
  def _normalize_reorder_payload(self, payload: list[JSONObject]) -> list[JSONObject]:
393
307
  """执行内部辅助逻辑。"""
394
308
  first_item = payload[0]
@@ -419,20 +333,3 @@ class ViewTools(ToolBase):
419
333
  if not view_keys:
420
334
  raise_tool_error(QingflowApiError.config_error("payload must include viewgraphKey/viewKey or ordinalType/viewKeyList"))
421
335
  return [{"ordinalType": "FIXED_VIEW_LIST", "viewKeyList": view_keys}]
422
-
423
-
424
- def _is_optional_view_list_error(error: QingflowApiError) -> bool:
425
- if is_auth_like_error(error):
426
- return False
427
- backend_code = backend_code_int(error)
428
- return backend_code in {40002, 40027, 404} or error.http_status == 404
429
-
430
-
431
- def _view_list_fallback_warning(code: str, error: QingflowApiError) -> JSONObject:
432
- return {
433
- "code": code,
434
- "message": "custom view list is unavailable; returned accessible system views instead",
435
- "backend_code": error.backend_code,
436
- "http_status": error.http_status,
437
- "request_id": error.request_id,
438
- }
@@ -14,25 +14,13 @@ class WorkflowTools(ToolBase):
14
14
 
15
15
  类型:流程配置工具。
16
16
  主要职责:
17
- 1. 查询流程节点与流程图配置;
18
- 2. 读取与更新流程规则;
19
- 3. 支持流程发布与流程调试辅助能力。
17
+ 1. 支持流程运行时校验与调试辅助能力;
18
+ 2. 保留 legacy 节点写路径供内部/测试直接调用(非 MCP 公开)。
19
+ 设计期流程配置读取统一走 WorkflowSpec(app_flow_get / GET /api/workflow/spec)。
20
20
  """
21
21
 
22
22
  def register(self, mcp: FastMCP) -> None:
23
23
  """注册当前工具到 MCP 服务。"""
24
- @mcp.tool()
25
- def workflow_list_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
26
- return self.workflow_list_nodes(profile=profile, app_key=app_key)
27
-
28
- @mcp.tool()
29
- def workflow_get_node_detail(profile: str = DEFAULT_PROFILE, app_key: str = "", audit_node_id: int = 0) -> JSONObject:
30
- return self.workflow_get_node_detail(profile=profile, app_key=app_key, audit_node_id=audit_node_id)
31
-
32
- @mcp.tool()
33
- def workflow_get_global_settings(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
34
- return self.workflow_get_global_settings(profile=profile, app_key=app_key)
35
-
36
24
  @mcp.tool()
37
25
  def workflow_get_future_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "", apply_id: int = 0) -> JSONObject:
38
26
  return self.workflow_get_future_nodes(profile=profile, app_key=app_key, apply_id=apply_id)
@@ -53,22 +41,6 @@ class WorkflowTools(ToolBase):
53
41
  audit_node_id=audit_node_id,
54
42
  )
55
43
 
56
- @mcp.tool()
57
- def workflow_get_qsource_active(profile: str = DEFAULT_PROFILE, app_key: str = "", qsource_id: int = 0) -> JSONObject:
58
- return self.workflow_get_qsource_active(profile=profile, app_key=app_key, qsource_id=qsource_id)
59
-
60
- @mcp.tool()
61
- def workflow_get_qsource_passive(profile: str = DEFAULT_PROFILE, app_key: str = "", qsource_id: int = 0) -> JSONObject:
62
- return self.workflow_get_qsource_passive(profile=profile, app_key=app_key, qsource_id=qsource_id)
63
-
64
- @mcp.tool()
65
- def workflow_get_editable_question_ids(profile: str = DEFAULT_PROFILE, app_key: str = "", audit_node_id: int = 0) -> JSONObject:
66
- return self.workflow_get_editable_question_ids(profile=profile, app_key=app_key, audit_node_id=audit_node_id)
67
-
68
- @mcp.tool()
69
- def workflow_get_print_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
70
- return self.workflow_get_print_nodes(profile=profile, app_key=app_key)
71
-
72
44
  @tool_cn_name("流程节点列表")
73
45
  def workflow_list_nodes(self, *, profile: str, app_key: str) -> JSONObject:
74
46
  """执行流程相关逻辑。"""
@@ -351,23 +323,7 @@ class WorkflowTools(ToolBase):
351
323
  for candidate_path in paths:
352
324
  try:
353
325
  result = self.backend.request("POST", call_context, candidate_path, json_body=json_body)
354
- fallback_applied = candidate_path != path or call_context is not context
355
- response: JSONObject = {
356
- "profile": profile,
357
- "ws_id": session_profile.selected_ws_id,
358
- "result": result,
359
- "request_path": candidate_path,
360
- "requested_path": path,
361
- "fallback_applied": fallback_applied,
362
- "qf_version_source": (
363
- call_context.qf_version_source
364
- or ("context" if call_context.qf_version else "unset")
365
- ),
366
- "verification": {
367
- "primary_path_used": candidate_path == path,
368
- "qf_version_used": call_context.qf_version is not None,
369
- },
370
- }
326
+ response: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
371
327
  response.update(extra)
372
328
  if risk_operation and risk_target:
373
329
  return self._attach_human_review_notice(response, operation=risk_operation, target=risk_target)
@@ -6,13 +6,10 @@ from mcp.server.fastmcp import FastMCP
6
6
 
7
7
  from ..backend_client import BackendRequestContext
8
8
  from ..config import DEFAULT_PROFILE
9
- from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
9
+ from ..errors import QingflowApiError, raise_tool_error
10
10
  from .base import ToolBase, tool_cn_name
11
11
 
12
12
 
13
- _WORKSPACE_DETAIL_WARNING_KEY = "_qingflow_workspace_detail_warning"
14
-
15
-
16
13
  class WorkspaceTools(ToolBase):
17
14
  """工作区工具(中文名:工作区与插件管理)。
18
15
 
@@ -94,14 +91,10 @@ class WorkspaceTools(ToolBase):
94
91
  normalized = result if isinstance(result, dict) else {"list": result if isinstance(result, list) else []}
95
92
  workspace_list = normalized.get("list") if isinstance(normalized.get("list"), list) else []
96
93
  selected_ws_id = _.selected_ws_id if _ is not None else None
97
- warnings: list[dict[str, Any]] = []
98
94
  if selected_ws_id and not any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list):
99
95
  try:
100
96
  selected_workspace = self.backend.request("GET", context, f"/user/workspace/{selected_ws_id}")
101
- except QingflowApiError as exc:
102
- if not _is_optional_workspace_detail_error(exc):
103
- raise
104
- warnings.append(_workspace_detail_fallback_warning(exc, ws_id=selected_ws_id))
97
+ except QingflowApiError:
105
98
  selected_workspace = {
106
99
  "wsId": selected_ws_id,
107
100
  "workspaceName": _.selected_ws_name if _ is not None else None,
@@ -116,14 +109,6 @@ class WorkspaceTools(ToolBase):
116
109
  "profile": profile,
117
110
  "include_external": include_external,
118
111
  "page": normalized,
119
- "warnings": warnings,
120
- "verification": {
121
- "selected_workspace_detail_verified": not warnings,
122
- "selected_workspace_included": bool(
123
- selected_ws_id
124
- and any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list)
125
- ),
126
- },
127
112
  }
128
113
 
129
114
  return self._run(profile, runner, require_workspace=False)
@@ -142,7 +127,6 @@ class WorkspaceTools(ToolBase):
142
127
  if target_ws_id is None or target_ws_id <= 0:
143
128
  raise_tool_error(QingflowApiError.workspace_not_selected(profile))
144
129
  workspace = self._fetch_workspace_with_fallback(context, ws_id=target_ws_id)
145
- workspace, warnings = _strip_workspace_detail_warning(workspace)
146
130
  system_version = self._workspace_system_version(workspace)
147
131
  return {
148
132
  "profile": profile,
@@ -150,11 +134,6 @@ class WorkspaceTools(ToolBase):
150
134
  "qf_version": system_version,
151
135
  "qf_version_source": "workspace_system_version" if system_version else "unverified",
152
136
  "workspace": workspace,
153
- "warnings": warnings,
154
- "verification": {
155
- "workspace_detail_verified": not warnings,
156
- "workspace_list_fallback_used": bool(warnings),
157
- },
158
137
  }
159
138
 
160
139
  return self._run(profile, runner, require_workspace=False)
@@ -172,7 +151,6 @@ class WorkspaceTools(ToolBase):
172
151
 
173
152
  def runner(_, context):
174
153
  workspace = self._fetch_workspace_with_fallback(context, ws_id=ws_id)
175
- workspace, warnings = _strip_workspace_detail_warning(workspace)
176
154
  workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or workspace.get("remark") or "").strip() or None
177
155
  selected = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=workspace_name)
178
156
  system_version = self._workspace_system_version(workspace)
@@ -193,11 +171,6 @@ class WorkspaceTools(ToolBase):
193
171
  "ws_id": selected.selected_ws_id,
194
172
  "workspace_name": selected.selected_ws_name,
195
173
  },
196
- "warnings": warnings,
197
- "verification": {
198
- "workspace_detail_verified": not warnings,
199
- "workspace_list_fallback_used": bool(warnings),
200
- },
201
174
  }
202
175
 
203
176
  return self._run(profile, runner, require_workspace=False)
@@ -239,15 +212,7 @@ class WorkspaceTools(ToolBase):
239
212
  *,
240
213
  ws_id: int,
241
214
  ) -> dict[str, Any]:
242
- try:
243
- workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
244
- except QingflowApiError as error:
245
- if not _is_optional_workspace_detail_error(error):
246
- raise
247
- fallback = self._fetch_workspace_from_list(context, ws_id=ws_id)
248
- if isinstance(fallback, dict):
249
- return _with_workspace_detail_warning(fallback, error, ws_id=ws_id)
250
- raise
215
+ workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
251
216
  if not isinstance(workspace, dict):
252
217
  raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
253
218
  if self._workspace_needs_list_fallback(workspace):
@@ -299,36 +264,3 @@ class WorkspaceTools(ToolBase):
299
264
  return None
300
265
  normalized = str(value).strip()
301
266
  return normalized or None
302
-
303
-
304
- def _is_optional_workspace_detail_error(error: QingflowApiError) -> bool:
305
- if is_auth_like_error(error):
306
- return False
307
- backend_code = backend_code_int(error)
308
- return backend_code in {40002, 40027, 404, 59004} or error.http_status == 404
309
-
310
-
311
- def _with_workspace_detail_warning(workspace: dict[str, Any], error: QingflowApiError, *, ws_id: int) -> dict[str, Any]:
312
- enriched = dict(workspace)
313
- enriched[_WORKSPACE_DETAIL_WARNING_KEY] = _workspace_detail_fallback_warning(error, ws_id=ws_id)
314
- return enriched
315
-
316
-
317
- def _strip_workspace_detail_warning(workspace: dict[str, Any]) -> tuple[dict[str, Any], list[dict[str, Any]]]:
318
- public_workspace = dict(workspace)
319
- warning = public_workspace.pop(_WORKSPACE_DETAIL_WARNING_KEY, None)
320
- warnings = [warning] if isinstance(warning, dict) else []
321
- return public_workspace, warnings
322
-
323
-
324
- def _workspace_detail_fallback_warning(error: QingflowApiError, *, ws_id: int) -> dict[str, Any]:
325
- return {
326
- "code": "WORKSPACE_DETAIL_UNAVAILABLE",
327
- "message": (
328
- f"workspace detail route for ws_id {ws_id} was unavailable; "
329
- "the tool used the visible workspace list as fallback."
330
- ),
331
- "backend_code": error.backend_code,
332
- "http_status": error.http_status,
333
- "request_id": error.request_id,
334
- }
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import { getPackageRoot, runSkillsCli } from "../lib/runtime.mjs";
3
-
4
- const packageRoot = getPackageRoot(import.meta.url);
5
- runSkillsCli(packageRoot, process.argv.slice(2));