@josephyan/qingflow-cli 0.2.0-beta.1000
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 +31 -0
- package/docs/local-agent-install.md +309 -0
- package/entry_point.py +13 -0
- package/npm/bin/qingflow.mjs +5 -0
- package/npm/lib/runtime.mjs +346 -0
- package/npm/scripts/postinstall.mjs +16 -0
- package/package.json +34 -0
- package/pyproject.toml +67 -0
- package/qingflow +15 -0
- package/src/qingflow_mcp/__init__.py +37 -0
- package/src/qingflow_mcp/__main__.py +5 -0
- package/src/qingflow_mcp/backend_client.py +649 -0
- package/src/qingflow_mcp/builder_facade/__init__.py +3 -0
- package/src/qingflow_mcp/builder_facade/models.py +1846 -0
- package/src/qingflow_mcp/builder_facade/service.py +16502 -0
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +18 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +112 -0
- package/src/qingflow_mcp/cli/commands/builder.py +539 -0
- package/src/qingflow_mcp/cli/commands/chart.py +18 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/portal.py +25 -0
- package/src/qingflow_mcp/cli/commands/record.py +331 -0
- package/src/qingflow_mcp/cli/commands/repo.py +80 -0
- package/src/qingflow_mcp/cli/commands/task.py +141 -0
- package/src/qingflow_mcp/cli/commands/view.py +18 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +110 -0
- package/src/qingflow_mcp/cli/context.py +60 -0
- package/src/qingflow_mcp/cli/formatters.py +573 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +186 -0
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +173 -0
- package/src/qingflow_mcp/config.py +407 -0
- package/src/qingflow_mcp/errors.py +66 -0
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/json_types.py +18 -0
- package/src/qingflow_mcp/list_type_labels.py +76 -0
- package/src/qingflow_mcp/public_surface.py +243 -0
- package/src/qingflow_mcp/repository_store.py +71 -0
- package/src/qingflow_mcp/response_trim.py +841 -0
- package/src/qingflow_mcp/server.py +216 -0
- package/src/qingflow_mcp/server_app_builder.py +543 -0
- package/src/qingflow_mcp/server_app_user.py +386 -0
- package/src/qingflow_mcp/session_store.py +369 -0
- package/src/qingflow_mcp/solution/__init__.py +6 -0
- package/src/qingflow_mcp/solution/build_assembly_store.py +181 -0
- package/src/qingflow_mcp/solution/compiler/__init__.py +282 -0
- package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +495 -0
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +187 -0
- package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
- package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
- package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/design_session.py +222 -0
- package/src/qingflow_mcp/solution/design_store.py +100 -0
- package/src/qingflow_mcp/solution/executor.py +2398 -0
- package/src/qingflow_mcp/solution/normalizer.py +23 -0
- package/src/qingflow_mcp/solution/requirements_builder.py +536 -0
- package/src/qingflow_mcp/solution/run_store.py +244 -0
- package/src/qingflow_mcp/solution/spec_models.py +855 -0
- package/src/qingflow_mcp/tools/__init__.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +3449 -0
- package/src/qingflow_mcp/tools/app_tools.py +926 -0
- package/src/qingflow_mcp/tools/approval_tools.py +1062 -0
- package/src/qingflow_mcp/tools/auth_tools.py +1133 -0
- package/src/qingflow_mcp/tools/base.py +281 -0
- package/src/qingflow_mcp/tools/code_block_tools.py +777 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +202 -0
- package/src/qingflow_mcp/tools/directory_tools.py +675 -0
- package/src/qingflow_mcp/tools/feedback_tools.py +238 -0
- package/src/qingflow_mcp/tools/file_tools.py +409 -0
- package/src/qingflow_mcp/tools/import_tools.py +2223 -0
- package/src/qingflow_mcp/tools/navigation_tools.py +210 -0
- package/src/qingflow_mcp/tools/package_tools.py +326 -0
- package/src/qingflow_mcp/tools/portal_tools.py +158 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +374 -0
- package/src/qingflow_mcp/tools/record_tools.py +14291 -0
- package/src/qingflow_mcp/tools/repository_dev_tools.py +552 -0
- package/src/qingflow_mcp/tools/resource_read_tools.py +503 -0
- package/src/qingflow_mcp/tools/role_tools.py +112 -0
- package/src/qingflow_mcp/tools/solution_tools.py +4054 -0
- package/src/qingflow_mcp/tools/task_context_tools.py +2986 -0
- package/src/qingflow_mcp/tools/task_tools.py +889 -0
- package/src/qingflow_mcp/tools/view_tools.py +335 -0
- package/src/qingflow_mcp/tools/workflow_tools.py +376 -0
- package/src/qingflow_mcp/tools/workspace_tools.py +266 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
from ..backend_client import BackendRequestContext
|
|
6
|
+
from ..config import DEFAULT_PROFILE
|
|
7
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
8
|
+
from ..json_types import JSONObject, JSONValue
|
|
9
|
+
from .base import ToolBase, tool_cn_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkflowTools(ToolBase):
|
|
13
|
+
"""流程工具(中文名:流程节点与规则管理)。
|
|
14
|
+
|
|
15
|
+
类型:流程配置工具。
|
|
16
|
+
主要职责:
|
|
17
|
+
1. 查询流程节点与流程图配置;
|
|
18
|
+
2. 读取与更新流程规则;
|
|
19
|
+
3. 支持流程发布与流程调试辅助能力。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def register(self, mcp: FastMCP) -> None:
|
|
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
|
+
@mcp.tool()
|
|
37
|
+
def workflow_get_future_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "", apply_id: int = 0) -> JSONObject:
|
|
38
|
+
return self.workflow_get_future_nodes(profile=profile, app_key=app_key, apply_id=apply_id)
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
def workflow_get_future_nodes_app(
|
|
42
|
+
profile: str = DEFAULT_PROFILE,
|
|
43
|
+
app_key: str = "",
|
|
44
|
+
apply_id: int = 0,
|
|
45
|
+
role: int = 1,
|
|
46
|
+
audit_node_id: int | None = None,
|
|
47
|
+
) -> JSONObject:
|
|
48
|
+
return self.workflow_get_future_nodes_app(
|
|
49
|
+
profile=profile,
|
|
50
|
+
app_key=app_key,
|
|
51
|
+
apply_id=apply_id,
|
|
52
|
+
role=role,
|
|
53
|
+
audit_node_id=audit_node_id,
|
|
54
|
+
)
|
|
55
|
+
|
|
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
|
+
@tool_cn_name("流程节点列表")
|
|
73
|
+
def workflow_list_nodes(self, *, profile: str, app_key: str) -> JSONObject:
|
|
74
|
+
"""执行流程相关逻辑。"""
|
|
75
|
+
self._require_app_key(app_key)
|
|
76
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNodes", app_key=app_key)
|
|
77
|
+
|
|
78
|
+
@tool_cn_name("流程节点详情")
|
|
79
|
+
def workflow_get_node_detail(self, *, profile: str, app_key: str, audit_node_id: int) -> JSONObject:
|
|
80
|
+
"""执行流程相关逻辑。"""
|
|
81
|
+
self._require_app_key(app_key)
|
|
82
|
+
self._require_positive("audit_node_id", audit_node_id)
|
|
83
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNodes/{audit_node_id}", app_key=app_key, audit_node_id=audit_node_id)
|
|
84
|
+
|
|
85
|
+
@tool_cn_name("新增流程节点")
|
|
86
|
+
def workflow_add_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
87
|
+
"""执行流程相关逻辑。"""
|
|
88
|
+
self._require_app_key(app_key)
|
|
89
|
+
body = self._require_dict(payload)
|
|
90
|
+
return self._request_with_post_fallbacks(
|
|
91
|
+
profile=profile,
|
|
92
|
+
app_key=app_key,
|
|
93
|
+
path=f"/app/{app_key}/auditNodes",
|
|
94
|
+
json_body=body,
|
|
95
|
+
alternate_paths=[f"/app/{app_key}/auditNode"],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@tool_cn_name("更新流程节点")
|
|
99
|
+
def workflow_update_node(self, *, profile: str, app_key: str, audit_node_id: int, payload: JSONObject) -> JSONObject:
|
|
100
|
+
"""执行流程相关逻辑。"""
|
|
101
|
+
self._require_app_key(app_key)
|
|
102
|
+
self._require_positive("audit_node_id", audit_node_id)
|
|
103
|
+
body = self._require_dict(payload)
|
|
104
|
+
return self._request_with_post_fallbacks(
|
|
105
|
+
profile=profile,
|
|
106
|
+
app_key=app_key,
|
|
107
|
+
path=f"/app/{app_key}/auditNodes/{audit_node_id}",
|
|
108
|
+
json_body=body,
|
|
109
|
+
alternate_paths=[f"/app/{app_key}/auditNode/{audit_node_id}"],
|
|
110
|
+
risk_operation="update",
|
|
111
|
+
risk_target="workflow node configuration",
|
|
112
|
+
audit_node_id=audit_node_id,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@tool_cn_name("删除流程节点")
|
|
116
|
+
def workflow_delete_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
117
|
+
"""执行流程相关逻辑。"""
|
|
118
|
+
self._require_app_key(app_key)
|
|
119
|
+
body = self._require_dict(payload)
|
|
120
|
+
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow node configuration")
|
|
121
|
+
|
|
122
|
+
@tool_cn_name("复制粘贴流程节点")
|
|
123
|
+
def workflow_copy_paste_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
124
|
+
"""执行流程相关逻辑。"""
|
|
125
|
+
self._require_app_key(app_key)
|
|
126
|
+
body = self._require_dict(payload)
|
|
127
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/copyAndPaste", app_key=app_key, json_body=body)
|
|
128
|
+
|
|
129
|
+
@tool_cn_name("剪切粘贴流程节点")
|
|
130
|
+
def workflow_cut_paste_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
131
|
+
"""执行流程相关逻辑。"""
|
|
132
|
+
self._require_app_key(app_key)
|
|
133
|
+
body = self._require_dict(payload)
|
|
134
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/cutAndPaste", app_key=app_key, json_body=body)
|
|
135
|
+
|
|
136
|
+
@tool_cn_name("创建流程分支")
|
|
137
|
+
def workflow_create_sub_branch(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
138
|
+
"""执行流程相关逻辑。"""
|
|
139
|
+
self._require_app_key(app_key)
|
|
140
|
+
body = self._require_dict(payload)
|
|
141
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/subBranch", app_key=app_key, json_body=body)
|
|
142
|
+
|
|
143
|
+
@tool_cn_name("删除流程分支")
|
|
144
|
+
def workflow_delete_sub_branch(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
145
|
+
"""执行流程相关逻辑。"""
|
|
146
|
+
self._require_app_key(app_key)
|
|
147
|
+
body = self._require_dict(payload)
|
|
148
|
+
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode/subBranch", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow branch configuration")
|
|
149
|
+
|
|
150
|
+
@tool_cn_name("流程全局设置")
|
|
151
|
+
def workflow_get_global_settings(self, *, profile: str, app_key: str) -> JSONObject:
|
|
152
|
+
"""执行流程相关逻辑。"""
|
|
153
|
+
self._require_app_key(app_key)
|
|
154
|
+
return self._request(profile, "GET", f"/app/{app_key}/workflow/global/setting", app_key=app_key)
|
|
155
|
+
|
|
156
|
+
@tool_cn_name("更新流程全局设置")
|
|
157
|
+
def workflow_update_global_settings(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
158
|
+
"""执行流程相关逻辑。"""
|
|
159
|
+
self._require_app_key(app_key)
|
|
160
|
+
body = self._require_dict(payload)
|
|
161
|
+
return self._request_with_post_fallbacks(
|
|
162
|
+
profile=profile,
|
|
163
|
+
app_key=app_key,
|
|
164
|
+
path=f"/app/{app_key}/workflow/global/setting",
|
|
165
|
+
json_body=body,
|
|
166
|
+
alternate_paths=[],
|
|
167
|
+
risk_operation="update",
|
|
168
|
+
risk_target="workflow global settings",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@tool_cn_name("发布流程")
|
|
172
|
+
def workflow_publish(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
173
|
+
"""执行流程相关逻辑。"""
|
|
174
|
+
self._require_app_key(app_key)
|
|
175
|
+
body = self._require_dict(payload)
|
|
176
|
+
return self._request_with_post_fallbacks(
|
|
177
|
+
profile=profile,
|
|
178
|
+
app_key=app_key,
|
|
179
|
+
path=f"/app/{app_key}/publish",
|
|
180
|
+
json_body=body,
|
|
181
|
+
alternate_paths=[],
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@tool_cn_name("流程后续节点")
|
|
185
|
+
def workflow_get_future_nodes(self, *, profile: str, app_key: str, apply_id: int) -> JSONObject:
|
|
186
|
+
"""执行流程相关逻辑。"""
|
|
187
|
+
self._require_app_key(app_key)
|
|
188
|
+
self._require_positive("apply_id", apply_id)
|
|
189
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNode/futureList/{apply_id}", app_key=app_key, apply_id=apply_id)
|
|
190
|
+
|
|
191
|
+
@tool_cn_name("流程后续节点(应用)")
|
|
192
|
+
def workflow_get_future_nodes_app(
|
|
193
|
+
self,
|
|
194
|
+
*,
|
|
195
|
+
profile: str,
|
|
196
|
+
app_key: str,
|
|
197
|
+
apply_id: int,
|
|
198
|
+
role: int,
|
|
199
|
+
audit_node_id: int | None,
|
|
200
|
+
) -> JSONObject:
|
|
201
|
+
"""执行流程相关逻辑。"""
|
|
202
|
+
self._require_app_key(app_key)
|
|
203
|
+
self._require_positive("apply_id", apply_id)
|
|
204
|
+
params: JSONObject = {"role": role}
|
|
205
|
+
if audit_node_id is not None:
|
|
206
|
+
self._require_positive("audit_node_id", audit_node_id)
|
|
207
|
+
params["auditNodeId"] = audit_node_id
|
|
208
|
+
return self._request(
|
|
209
|
+
profile,
|
|
210
|
+
"GET",
|
|
211
|
+
f"/app/{app_key}/auditNode/appFutureListV2/{apply_id}",
|
|
212
|
+
app_key=app_key,
|
|
213
|
+
apply_id=apply_id,
|
|
214
|
+
params=params,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@tool_cn_name("流程 Webhook 测试")
|
|
218
|
+
def workflow_webhook_test(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
219
|
+
"""执行流程相关逻辑。"""
|
|
220
|
+
self._require_app_key(app_key)
|
|
221
|
+
body = self._require_dict(payload)
|
|
222
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/webhookTest", app_key=app_key, json_body=body)
|
|
223
|
+
|
|
224
|
+
@tool_cn_name("流程 QSource 查询")
|
|
225
|
+
def workflow_qsource_query(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
226
|
+
"""执行流程相关逻辑。"""
|
|
227
|
+
self._require_app_key(app_key)
|
|
228
|
+
body = self._require_dict(payload)
|
|
229
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/qSourceQuery", app_key=app_key, json_body=body)
|
|
230
|
+
|
|
231
|
+
@tool_cn_name("流程 QSource 测试")
|
|
232
|
+
def workflow_qsource_test(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
233
|
+
"""执行流程相关逻辑。"""
|
|
234
|
+
self._require_app_key(app_key)
|
|
235
|
+
body = self._require_dict(payload)
|
|
236
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/qSourceQueryTest", app_key=app_key, json_body=body)
|
|
237
|
+
|
|
238
|
+
@tool_cn_name("流程主动 QSource 配置")
|
|
239
|
+
def workflow_get_qsource_active(self, *, profile: str, app_key: str, qsource_id: int) -> JSONObject:
|
|
240
|
+
"""执行流程相关逻辑。"""
|
|
241
|
+
self._require_app_key(app_key)
|
|
242
|
+
self._require_positive("qsource_id", qsource_id)
|
|
243
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNode/active/qsource", app_key=app_key, params={"qSourceId": qsource_id})
|
|
244
|
+
|
|
245
|
+
@tool_cn_name("更新主动 QSource 配置")
|
|
246
|
+
def workflow_upsert_qsource_active(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
247
|
+
"""执行流程相关逻辑。"""
|
|
248
|
+
self._require_app_key(app_key)
|
|
249
|
+
body = self._require_dict(payload)
|
|
250
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/active/qsource", app_key=app_key, json_body=body)
|
|
251
|
+
|
|
252
|
+
@tool_cn_name("流程被动 QSource 配置")
|
|
253
|
+
def workflow_get_qsource_passive(self, *, profile: str, app_key: str, qsource_id: int) -> JSONObject:
|
|
254
|
+
"""执行流程相关逻辑。"""
|
|
255
|
+
self._require_app_key(app_key)
|
|
256
|
+
self._require_positive("qsource_id", qsource_id)
|
|
257
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNode/passive/qsource", app_key=app_key, params={"qSourceId": qsource_id})
|
|
258
|
+
|
|
259
|
+
@tool_cn_name("更新被动 QSource 配置")
|
|
260
|
+
def workflow_upsert_qsource_passive(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
261
|
+
"""执行流程相关逻辑。"""
|
|
262
|
+
self._require_app_key(app_key)
|
|
263
|
+
body = self._require_dict(payload)
|
|
264
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/passive/qsource", app_key=app_key, json_body=body)
|
|
265
|
+
|
|
266
|
+
@tool_cn_name("切换 QSource 状态")
|
|
267
|
+
def workflow_switch_qsource_status(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
268
|
+
"""执行流程相关逻辑。"""
|
|
269
|
+
self._require_app_key(app_key)
|
|
270
|
+
body = self._require_dict(payload)
|
|
271
|
+
return self._request(profile, "POST", f"/app/{app_key}/auditNode/source/status", app_key=app_key, json_body=body)
|
|
272
|
+
|
|
273
|
+
@tool_cn_name("删除 QSource 配置")
|
|
274
|
+
def workflow_delete_qsource(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
275
|
+
"""执行流程相关逻辑。"""
|
|
276
|
+
self._require_app_key(app_key)
|
|
277
|
+
body = self._require_dict(payload)
|
|
278
|
+
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode/qsource", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow qsource configuration")
|
|
279
|
+
|
|
280
|
+
@tool_cn_name("节点可编辑字段")
|
|
281
|
+
def workflow_get_editable_question_ids(self, *, profile: str, app_key: str, audit_node_id: int) -> JSONObject:
|
|
282
|
+
"""执行流程相关逻辑。"""
|
|
283
|
+
self._require_app_key(app_key)
|
|
284
|
+
self._require_positive("audit_node_id", audit_node_id)
|
|
285
|
+
return self._request(
|
|
286
|
+
profile,
|
|
287
|
+
"GET",
|
|
288
|
+
f"/app/{app_key}/auditNode/{audit_node_id}/editableQueIds",
|
|
289
|
+
app_key=app_key,
|
|
290
|
+
audit_node_id=audit_node_id,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@tool_cn_name("流程打印节点")
|
|
294
|
+
def workflow_get_print_nodes(self, *, profile: str, app_key: str) -> JSONObject:
|
|
295
|
+
"""执行流程相关逻辑。"""
|
|
296
|
+
self._require_app_key(app_key)
|
|
297
|
+
return self._request(profile, "GET", f"/app/{app_key}/auditNode/printNodes", app_key=app_key)
|
|
298
|
+
|
|
299
|
+
def _request(
|
|
300
|
+
self,
|
|
301
|
+
profile: str,
|
|
302
|
+
method: str,
|
|
303
|
+
path: str,
|
|
304
|
+
*,
|
|
305
|
+
app_key: str,
|
|
306
|
+
json_body: JSONValue = None,
|
|
307
|
+
params: JSONObject | None = None,
|
|
308
|
+
risk_operation: str | None = None,
|
|
309
|
+
risk_target: str | None = None,
|
|
310
|
+
**extra: JSONValue,
|
|
311
|
+
) -> JSONObject:
|
|
312
|
+
"""执行内部辅助逻辑。"""
|
|
313
|
+
def runner(session_profile, context):
|
|
314
|
+
result = self.backend.request(method, context, path, json_body=json_body, params=params)
|
|
315
|
+
response: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
|
|
316
|
+
response.update(extra)
|
|
317
|
+
if risk_operation and risk_target:
|
|
318
|
+
return self._attach_human_review_notice(response, operation=risk_operation, target=risk_target)
|
|
319
|
+
return response
|
|
320
|
+
|
|
321
|
+
return self._run(profile, runner)
|
|
322
|
+
|
|
323
|
+
def _request_with_post_fallbacks(
|
|
324
|
+
self,
|
|
325
|
+
*,
|
|
326
|
+
profile: str,
|
|
327
|
+
app_key: str,
|
|
328
|
+
path: str,
|
|
329
|
+
json_body: JSONObject,
|
|
330
|
+
alternate_paths: list[str],
|
|
331
|
+
risk_operation: str | None = None,
|
|
332
|
+
risk_target: str | None = None,
|
|
333
|
+
**extra: JSONValue,
|
|
334
|
+
) -> JSONObject:
|
|
335
|
+
"""执行内部辅助逻辑。"""
|
|
336
|
+
def runner(session_profile, context):
|
|
337
|
+
attempted_contexts = [context]
|
|
338
|
+
if context.qf_version is not None:
|
|
339
|
+
attempted_contexts.append(
|
|
340
|
+
BackendRequestContext(
|
|
341
|
+
base_url=context.base_url,
|
|
342
|
+
token=context.token,
|
|
343
|
+
ws_id=context.ws_id,
|
|
344
|
+
qf_version=None,
|
|
345
|
+
qf_version_source="workflow_retry_without_qf_version",
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
paths = [path, *alternate_paths]
|
|
349
|
+
last_error: QingflowApiError | None = None
|
|
350
|
+
for call_context in attempted_contexts:
|
|
351
|
+
for candidate_path in paths:
|
|
352
|
+
try:
|
|
353
|
+
result = self.backend.request("POST", call_context, candidate_path, json_body=json_body)
|
|
354
|
+
response: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
|
|
355
|
+
response.update(extra)
|
|
356
|
+
if risk_operation and risk_target:
|
|
357
|
+
return self._attach_human_review_notice(response, operation=risk_operation, target=risk_target)
|
|
358
|
+
return response
|
|
359
|
+
except QingflowApiError as error:
|
|
360
|
+
last_error = error
|
|
361
|
+
if error.http_status != 404:
|
|
362
|
+
raise
|
|
363
|
+
assert last_error is not None
|
|
364
|
+
raise last_error
|
|
365
|
+
|
|
366
|
+
return self._run(profile, runner)
|
|
367
|
+
|
|
368
|
+
def _require_app_key(self, app_key: str) -> None:
|
|
369
|
+
"""执行内部辅助逻辑。"""
|
|
370
|
+
if not app_key:
|
|
371
|
+
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
372
|
+
|
|
373
|
+
def _require_positive(self, field_name: str, value: int) -> None:
|
|
374
|
+
"""执行内部辅助逻辑。"""
|
|
375
|
+
if value <= 0:
|
|
376
|
+
raise_tool_error(QingflowApiError.config_error(f"{field_name} must be positive"))
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
from ..backend_client import BackendRequestContext
|
|
8
|
+
from ..config import DEFAULT_PROFILE
|
|
9
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
10
|
+
from .base import ToolBase, tool_cn_name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkspaceTools(ToolBase):
|
|
14
|
+
"""工作区工具(中文名:工作区与插件管理)。
|
|
15
|
+
|
|
16
|
+
类型:基础上下文工具。
|
|
17
|
+
主要职责:
|
|
18
|
+
1. 查询当前用户可见工作区列表;
|
|
19
|
+
2. 管理工作区插件安装状态。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def register(self, mcp: FastMCP) -> None:
|
|
23
|
+
"""注册当前工具到 MCP 服务。"""
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
def workspace_list(
|
|
26
|
+
profile: str = DEFAULT_PROFILE,
|
|
27
|
+
page_num: int = 1,
|
|
28
|
+
page_size: int = 20,
|
|
29
|
+
include_external: bool = False,
|
|
30
|
+
) -> dict[str, Any]:
|
|
31
|
+
return self.workspace_list(
|
|
32
|
+
profile=profile,
|
|
33
|
+
page_num=page_num,
|
|
34
|
+
page_size=page_size,
|
|
35
|
+
include_external=include_external,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def workspace_get(
|
|
40
|
+
profile: str = DEFAULT_PROFILE,
|
|
41
|
+
ws_id: int = 0,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
return self.workspace_get(
|
|
44
|
+
profile=profile,
|
|
45
|
+
ws_id=ws_id if ws_id > 0 else None,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
def workspace_select(
|
|
50
|
+
profile: str = DEFAULT_PROFILE,
|
|
51
|
+
ws_id: int = 0,
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
return self.workspace_select(
|
|
54
|
+
profile=profile,
|
|
55
|
+
ws_id=ws_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@mcp.tool()
|
|
59
|
+
def workspace_set_plugin_status(
|
|
60
|
+
profile: str = DEFAULT_PROFILE,
|
|
61
|
+
plugin_id: int = 0,
|
|
62
|
+
being_installed: bool = True,
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
return self.workspace_set_plugin_status(
|
|
65
|
+
profile=profile,
|
|
66
|
+
plugin_id=plugin_id,
|
|
67
|
+
being_installed=being_installed,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@tool_cn_name("工作区列表")
|
|
71
|
+
def workspace_list(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
profile: str = DEFAULT_PROFILE,
|
|
75
|
+
page_num: int = 1,
|
|
76
|
+
page_size: int = 20,
|
|
77
|
+
include_external: bool = False,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""执行工作区相关逻辑。"""
|
|
80
|
+
if page_num <= 0 or page_size <= 0:
|
|
81
|
+
raise_tool_error(QingflowApiError.config_error("page_num and page_size must be positive"))
|
|
82
|
+
|
|
83
|
+
def runner(_, context):
|
|
84
|
+
path = "/user/allWorkspaceList/pageQuery" if include_external else "/user/workspaceList/pageQuery"
|
|
85
|
+
result = self.backend.request(
|
|
86
|
+
"POST",
|
|
87
|
+
context,
|
|
88
|
+
path,
|
|
89
|
+
json_body={"pageNum": page_num, "pageSize": page_size, "authList": [0, 1, 2]},
|
|
90
|
+
)
|
|
91
|
+
normalized = result if isinstance(result, dict) else {"list": result if isinstance(result, list) else []}
|
|
92
|
+
workspace_list = normalized.get("list") if isinstance(normalized.get("list"), list) else []
|
|
93
|
+
selected_ws_id = _.selected_ws_id if _ is not None else None
|
|
94
|
+
if selected_ws_id and not any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list):
|
|
95
|
+
try:
|
|
96
|
+
selected_workspace = self.backend.request("GET", context, f"/user/workspace/{selected_ws_id}")
|
|
97
|
+
except QingflowApiError:
|
|
98
|
+
selected_workspace = {
|
|
99
|
+
"wsId": selected_ws_id,
|
|
100
|
+
"workspaceName": _.selected_ws_name if _ is not None else None,
|
|
101
|
+
}
|
|
102
|
+
if isinstance(selected_workspace, dict):
|
|
103
|
+
workspace_list.append(selected_workspace)
|
|
104
|
+
normalized["list"] = workspace_list
|
|
105
|
+
total = normalized.get("total")
|
|
106
|
+
if isinstance(total, int):
|
|
107
|
+
normalized["total"] = total + 1
|
|
108
|
+
return {
|
|
109
|
+
"profile": profile,
|
|
110
|
+
"include_external": include_external,
|
|
111
|
+
"page": normalized,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return self._run(profile, runner, require_workspace=False)
|
|
115
|
+
|
|
116
|
+
@tool_cn_name("工作区详情")
|
|
117
|
+
def workspace_get(
|
|
118
|
+
self,
|
|
119
|
+
*,
|
|
120
|
+
profile: str = DEFAULT_PROFILE,
|
|
121
|
+
ws_id: int | None = None,
|
|
122
|
+
) -> dict[str, Any]:
|
|
123
|
+
"""读取单个工作区详情,并尽量补齐真实 systemVersion。"""
|
|
124
|
+
|
|
125
|
+
def runner(session_profile, context):
|
|
126
|
+
target_ws_id = ws_id or (session_profile.selected_ws_id if session_profile is not None else None)
|
|
127
|
+
if target_ws_id is None or target_ws_id <= 0:
|
|
128
|
+
raise_tool_error(QingflowApiError.workspace_not_selected(profile))
|
|
129
|
+
workspace = self._fetch_workspace_with_fallback(context, ws_id=target_ws_id)
|
|
130
|
+
system_version = self._workspace_system_version(workspace)
|
|
131
|
+
return {
|
|
132
|
+
"profile": profile,
|
|
133
|
+
"ws_id": target_ws_id,
|
|
134
|
+
"qf_version": system_version,
|
|
135
|
+
"qf_version_source": "workspace_system_version" if system_version else "unverified",
|
|
136
|
+
"workspace": workspace,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return self._run(profile, runner, require_workspace=False)
|
|
140
|
+
|
|
141
|
+
@tool_cn_name("切换工作区")
|
|
142
|
+
def workspace_select(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
profile: str = DEFAULT_PROFILE,
|
|
146
|
+
ws_id: int,
|
|
147
|
+
) -> dict[str, Any]:
|
|
148
|
+
"""切换当前 profile 选中的工作区,并尽量同步真实 systemVersion。"""
|
|
149
|
+
if ws_id <= 0:
|
|
150
|
+
raise_tool_error(QingflowApiError.config_error("ws_id must be positive"))
|
|
151
|
+
|
|
152
|
+
def runner(_, context):
|
|
153
|
+
workspace = self._fetch_workspace_with_fallback(context, ws_id=ws_id)
|
|
154
|
+
workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or workspace.get("remark") or "").strip() or None
|
|
155
|
+
selected = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=workspace_name)
|
|
156
|
+
system_version = self._workspace_system_version(workspace)
|
|
157
|
+
qf_version_source = "workspace_system_version" if system_version else "unverified"
|
|
158
|
+
if system_version:
|
|
159
|
+
selected = self.sessions.update_route(
|
|
160
|
+
profile,
|
|
161
|
+
qf_version=system_version,
|
|
162
|
+
qf_version_source=qf_version_source,
|
|
163
|
+
)
|
|
164
|
+
return {
|
|
165
|
+
"profile": profile,
|
|
166
|
+
"ws_id": ws_id,
|
|
167
|
+
"qf_version": selected.qf_version,
|
|
168
|
+
"qf_version_source": selected.qf_version_source or qf_version_source,
|
|
169
|
+
"workspace": workspace,
|
|
170
|
+
"selected": {
|
|
171
|
+
"ws_id": selected.selected_ws_id,
|
|
172
|
+
"workspace_name": selected.selected_ws_name,
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return self._run(profile, runner, require_workspace=False)
|
|
177
|
+
|
|
178
|
+
@tool_cn_name("设置工作区插件状态")
|
|
179
|
+
def workspace_set_plugin_status(
|
|
180
|
+
self,
|
|
181
|
+
*,
|
|
182
|
+
profile: str = DEFAULT_PROFILE,
|
|
183
|
+
plugin_id: int,
|
|
184
|
+
being_installed: bool = True,
|
|
185
|
+
) -> dict[str, Any]:
|
|
186
|
+
"""执行工作区相关逻辑。"""
|
|
187
|
+
if plugin_id <= 0:
|
|
188
|
+
raise_tool_error(QingflowApiError.config_error("plugin_id must be positive"))
|
|
189
|
+
|
|
190
|
+
def runner(_, context):
|
|
191
|
+
result = self.backend.request(
|
|
192
|
+
"POST",
|
|
193
|
+
context,
|
|
194
|
+
"/ws/plugin",
|
|
195
|
+
json_body={
|
|
196
|
+
"pluginId": plugin_id,
|
|
197
|
+
"beingInstalled": being_installed,
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
return {
|
|
201
|
+
"profile": profile,
|
|
202
|
+
"plugin_id": plugin_id,
|
|
203
|
+
"being_installed": being_installed,
|
|
204
|
+
"result": result,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return self._run(profile, runner)
|
|
208
|
+
|
|
209
|
+
def _fetch_workspace_with_fallback(
|
|
210
|
+
self,
|
|
211
|
+
context: BackendRequestContext,
|
|
212
|
+
*,
|
|
213
|
+
ws_id: int,
|
|
214
|
+
) -> dict[str, Any]:
|
|
215
|
+
workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
|
|
216
|
+
if not isinstance(workspace, dict):
|
|
217
|
+
raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
|
|
218
|
+
if self._workspace_needs_list_fallback(workspace):
|
|
219
|
+
fallback = self._fetch_workspace_from_list(context, ws_id=ws_id)
|
|
220
|
+
if isinstance(fallback, dict):
|
|
221
|
+
merged = dict(workspace)
|
|
222
|
+
for key, value in fallback.items():
|
|
223
|
+
if merged.get(key) in (None, "") and value not in (None, ""):
|
|
224
|
+
merged[key] = value
|
|
225
|
+
workspace = merged
|
|
226
|
+
return workspace
|
|
227
|
+
|
|
228
|
+
def _fetch_workspace_from_list(self, context: BackendRequestContext, *, ws_id: int) -> dict[str, Any] | None:
|
|
229
|
+
payload = self.backend.request(
|
|
230
|
+
"POST",
|
|
231
|
+
BackendRequestContext(
|
|
232
|
+
base_url=context.base_url,
|
|
233
|
+
token=context.token,
|
|
234
|
+
ws_id=None,
|
|
235
|
+
qf_version=context.qf_version,
|
|
236
|
+
qf_version_source=context.qf_version_source,
|
|
237
|
+
),
|
|
238
|
+
"/user/workspaceList/pageQuery",
|
|
239
|
+
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2, 3]},
|
|
240
|
+
)
|
|
241
|
+
workspaces = payload.get("list") if isinstance(payload, dict) else []
|
|
242
|
+
if not isinstance(workspaces, list):
|
|
243
|
+
return None
|
|
244
|
+
found = next(
|
|
245
|
+
(
|
|
246
|
+
item
|
|
247
|
+
for item in workspaces
|
|
248
|
+
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
249
|
+
),
|
|
250
|
+
None,
|
|
251
|
+
)
|
|
252
|
+
return found if isinstance(found, dict) else None
|
|
253
|
+
|
|
254
|
+
def _workspace_needs_list_fallback(self, workspace: dict[str, Any]) -> bool:
|
|
255
|
+
workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or "").strip()
|
|
256
|
+
system_version = self._workspace_system_version(workspace)
|
|
257
|
+
return not workspace_name or system_version is None
|
|
258
|
+
|
|
259
|
+
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
260
|
+
if not isinstance(workspace, dict):
|
|
261
|
+
return None
|
|
262
|
+
value = workspace.get("systemVersion")
|
|
263
|
+
if value is None:
|
|
264
|
+
return None
|
|
265
|
+
normalized = str(value).strip()
|
|
266
|
+
return normalized or None
|