@josephyan/qingflow-cli 0.2.0-beta.61 → 0.2.0-beta.63
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/models.py +225 -0
- package/src/qingflow_mcp/builder_facade/service.py +1024 -30
- package/src/qingflow_mcp/cli/commands/builder.py +58 -1
- package/src/qingflow_mcp/cli/commands/task.py +3 -1
- package/src/qingflow_mcp/cli/formatters.py +36 -0
- package/src/qingflow_mcp/server_app_builder.py +25 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +244 -2
- package/src/qingflow_mcp/tools/approval_tools.py +147 -12
- package/src/qingflow_mcp/tools/custom_button_tools.py +177 -0
- package/src/qingflow_mcp/tools/import_tools.py +217 -90
- package/src/qingflow_mcp/tools/task_context_tools.py +621 -29
|
@@ -69,12 +69,24 @@ class ApprovalTools(ToolBase):
|
|
|
69
69
|
return self.record_comment_mark_read(profile=profile, app_key=app_key, apply_id=record_id)
|
|
70
70
|
|
|
71
71
|
@mcp.tool(description=self._high_risk_tool_description(operation="approve", target="workflow task"))
|
|
72
|
-
def task_approve(
|
|
73
|
-
|
|
72
|
+
def task_approve(
|
|
73
|
+
profile: str = DEFAULT_PROFILE,
|
|
74
|
+
app_key: str = "",
|
|
75
|
+
record_id: int = 0,
|
|
76
|
+
payload: dict[str, Any] | None = None,
|
|
77
|
+
fields: dict[str, Any] | None = None,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
return self.task_approve(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
74
80
|
|
|
75
81
|
@mcp.tool(description=self._high_risk_tool_description(operation="reject", target="workflow task"))
|
|
76
|
-
def task_reject(
|
|
77
|
-
|
|
82
|
+
def task_reject(
|
|
83
|
+
profile: str = DEFAULT_PROFILE,
|
|
84
|
+
app_key: str = "",
|
|
85
|
+
record_id: int = 0,
|
|
86
|
+
payload: dict[str, Any] | None = None,
|
|
87
|
+
fields: dict[str, Any] | None = None,
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
return self.task_reject(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
78
90
|
|
|
79
91
|
@mcp.tool()
|
|
80
92
|
def task_rollback_candidates(
|
|
@@ -86,12 +98,40 @@ class ApprovalTools(ToolBase):
|
|
|
86
98
|
return self.task_rollback_candidates(profile=profile, app_key=app_key, record_id=record_id, workflow_node_id=workflow_node_id)
|
|
87
99
|
|
|
88
100
|
@mcp.tool()
|
|
89
|
-
def task_rollback(
|
|
90
|
-
|
|
101
|
+
def task_rollback(
|
|
102
|
+
profile: str = DEFAULT_PROFILE,
|
|
103
|
+
app_key: str = "",
|
|
104
|
+
record_id: int = 0,
|
|
105
|
+
payload: dict[str, Any] | None = None,
|
|
106
|
+
fields: dict[str, Any] | None = None,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
return self.task_rollback(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
91
109
|
|
|
92
110
|
@mcp.tool()
|
|
93
|
-
def task_transfer(
|
|
94
|
-
|
|
111
|
+
def task_transfer(
|
|
112
|
+
profile: str = DEFAULT_PROFILE,
|
|
113
|
+
app_key: str = "",
|
|
114
|
+
record_id: int = 0,
|
|
115
|
+
payload: dict[str, Any] | None = None,
|
|
116
|
+
fields: dict[str, Any] | None = None,
|
|
117
|
+
) -> dict[str, Any]:
|
|
118
|
+
return self.task_transfer(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
|
|
119
|
+
|
|
120
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="save", target="workflow task fields without advancing the workflow"))
|
|
121
|
+
def task_save_only(
|
|
122
|
+
profile: str = DEFAULT_PROFILE,
|
|
123
|
+
app_key: str = "",
|
|
124
|
+
record_id: int = 0,
|
|
125
|
+
workflow_node_id: int = 0,
|
|
126
|
+
fields: dict[str, Any] | None = None,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
return self.task_save_only(
|
|
129
|
+
profile=profile,
|
|
130
|
+
app_key=app_key,
|
|
131
|
+
record_id=record_id,
|
|
132
|
+
workflow_node_id=workflow_node_id,
|
|
133
|
+
fields=fields or {},
|
|
134
|
+
)
|
|
95
135
|
|
|
96
136
|
@mcp.tool()
|
|
97
137
|
def task_transfer_candidates(
|
|
@@ -156,7 +196,17 @@ class ApprovalTools(ToolBase):
|
|
|
156
196
|
selection={"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword},
|
|
157
197
|
)
|
|
158
198
|
|
|
159
|
-
def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
199
|
+
def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
200
|
+
if fields:
|
|
201
|
+
return self._delegate_task_action(
|
|
202
|
+
profile=profile,
|
|
203
|
+
app_key=app_key,
|
|
204
|
+
record_id=record_id,
|
|
205
|
+
action="approve",
|
|
206
|
+
payload=payload,
|
|
207
|
+
fields=fields,
|
|
208
|
+
public_action="task_approve",
|
|
209
|
+
)
|
|
160
210
|
raw = self.record_approve(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
161
211
|
return self._public_action_response(
|
|
162
212
|
raw,
|
|
@@ -166,7 +216,17 @@ class ApprovalTools(ToolBase):
|
|
|
166
216
|
human_review=True,
|
|
167
217
|
)
|
|
168
218
|
|
|
169
|
-
def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
219
|
+
def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
220
|
+
if fields:
|
|
221
|
+
return self._delegate_task_action(
|
|
222
|
+
profile=profile,
|
|
223
|
+
app_key=app_key,
|
|
224
|
+
record_id=record_id,
|
|
225
|
+
action="reject",
|
|
226
|
+
payload=payload,
|
|
227
|
+
fields=fields,
|
|
228
|
+
public_action="task_reject",
|
|
229
|
+
)
|
|
170
230
|
raw = self.record_reject(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
171
231
|
return self._public_action_response(
|
|
172
232
|
raw,
|
|
@@ -186,7 +246,17 @@ class ApprovalTools(ToolBase):
|
|
|
186
246
|
selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id},
|
|
187
247
|
)
|
|
188
248
|
|
|
189
|
-
def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
249
|
+
def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
250
|
+
if fields:
|
|
251
|
+
return self._delegate_task_action(
|
|
252
|
+
profile=profile,
|
|
253
|
+
app_key=app_key,
|
|
254
|
+
record_id=record_id,
|
|
255
|
+
action="rollback",
|
|
256
|
+
payload=payload,
|
|
257
|
+
fields=fields,
|
|
258
|
+
public_action="task_rollback",
|
|
259
|
+
)
|
|
190
260
|
raw = self.record_rollback(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
191
261
|
return self._public_action_response(
|
|
192
262
|
raw,
|
|
@@ -196,7 +266,17 @@ class ApprovalTools(ToolBase):
|
|
|
196
266
|
human_review=True,
|
|
197
267
|
)
|
|
198
268
|
|
|
199
|
-
def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
|
|
269
|
+
def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
270
|
+
if fields:
|
|
271
|
+
return self._delegate_task_action(
|
|
272
|
+
profile=profile,
|
|
273
|
+
app_key=app_key,
|
|
274
|
+
record_id=record_id,
|
|
275
|
+
action="transfer",
|
|
276
|
+
payload=payload,
|
|
277
|
+
fields=fields,
|
|
278
|
+
public_action="task_transfer",
|
|
279
|
+
)
|
|
200
280
|
raw = self.record_transfer(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
201
281
|
return self._public_action_response(
|
|
202
282
|
raw,
|
|
@@ -206,6 +286,26 @@ class ApprovalTools(ToolBase):
|
|
|
206
286
|
human_review=True,
|
|
207
287
|
)
|
|
208
288
|
|
|
289
|
+
def task_save_only(
|
|
290
|
+
self,
|
|
291
|
+
*,
|
|
292
|
+
profile: str,
|
|
293
|
+
app_key: str,
|
|
294
|
+
record_id: int,
|
|
295
|
+
workflow_node_id: int,
|
|
296
|
+
fields: dict[str, Any],
|
|
297
|
+
) -> dict[str, Any]:
|
|
298
|
+
return self._delegate_task_action(
|
|
299
|
+
profile=profile,
|
|
300
|
+
app_key=app_key,
|
|
301
|
+
record_id=record_id,
|
|
302
|
+
action="save_only",
|
|
303
|
+
payload={},
|
|
304
|
+
fields=fields,
|
|
305
|
+
public_action="task_save_only",
|
|
306
|
+
workflow_node_id=workflow_node_id,
|
|
307
|
+
)
|
|
308
|
+
|
|
209
309
|
def task_transfer_candidates(
|
|
210
310
|
self,
|
|
211
311
|
*,
|
|
@@ -749,6 +849,41 @@ class ApprovalTools(ToolBase):
|
|
|
749
849
|
if payload.get("handSignImageUrl"):
|
|
750
850
|
raise_tool_error(QingflowApiError.not_supported("NOT_SUPPORTED_IN_V1: handSignImageUrl is not supported"))
|
|
751
851
|
|
|
852
|
+
def _delegate_task_action(
|
|
853
|
+
self,
|
|
854
|
+
*,
|
|
855
|
+
profile: str,
|
|
856
|
+
app_key: str,
|
|
857
|
+
record_id: int,
|
|
858
|
+
action: str,
|
|
859
|
+
payload: dict[str, Any],
|
|
860
|
+
fields: dict[str, Any],
|
|
861
|
+
public_action: str,
|
|
862
|
+
workflow_node_id: int | None = None,
|
|
863
|
+
) -> dict[str, Any]:
|
|
864
|
+
from .task_context_tools import TaskContextTools
|
|
865
|
+
|
|
866
|
+
node_id = workflow_node_id
|
|
867
|
+
if node_id is None:
|
|
868
|
+
node_payload = dict(payload or {})
|
|
869
|
+
node_id = self._extract_node_id(node_payload)
|
|
870
|
+
delegated = TaskContextTools(self.sessions, self.backend).task_action_execute(
|
|
871
|
+
profile=profile,
|
|
872
|
+
app_key=app_key,
|
|
873
|
+
record_id=record_id,
|
|
874
|
+
workflow_node_id=node_id,
|
|
875
|
+
action=action,
|
|
876
|
+
payload=payload or {},
|
|
877
|
+
fields=fields or {},
|
|
878
|
+
)
|
|
879
|
+
response = dict(delegated)
|
|
880
|
+
data = response.get("data")
|
|
881
|
+
if isinstance(data, dict):
|
|
882
|
+
payload_data = dict(data)
|
|
883
|
+
payload_data["action"] = public_action
|
|
884
|
+
response["data"] = payload_data
|
|
885
|
+
return response
|
|
886
|
+
|
|
752
887
|
def _public_page_response(
|
|
753
888
|
self,
|
|
754
889
|
raw: dict[str, Any],
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
|
|
5
|
+
from ..config import DEFAULT_PROFILE
|
|
6
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
7
|
+
from ..json_types import JSONObject
|
|
8
|
+
from .base import ToolBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomButtonTools(ToolBase):
|
|
12
|
+
def custom_button_list(
|
|
13
|
+
self,
|
|
14
|
+
*,
|
|
15
|
+
profile: str,
|
|
16
|
+
app_key: str,
|
|
17
|
+
being_draft: bool = True,
|
|
18
|
+
include_raw: bool = False,
|
|
19
|
+
) -> JSONObject:
|
|
20
|
+
self._require_app_key(app_key)
|
|
21
|
+
|
|
22
|
+
def runner(session_profile, context):
|
|
23
|
+
result = self.backend.request(
|
|
24
|
+
"GET",
|
|
25
|
+
context,
|
|
26
|
+
f"/app/{app_key}/customButton",
|
|
27
|
+
params={"beingDraft": being_draft},
|
|
28
|
+
)
|
|
29
|
+
items = []
|
|
30
|
+
raw_items = result.get("result") if isinstance(result, dict) and isinstance(result.get("result"), list) else []
|
|
31
|
+
for item in raw_items:
|
|
32
|
+
if not isinstance(item, dict):
|
|
33
|
+
continue
|
|
34
|
+
items.append(self._compact_button_base_info(item))
|
|
35
|
+
response = {
|
|
36
|
+
"profile": profile,
|
|
37
|
+
"ws_id": session_profile.selected_ws_id,
|
|
38
|
+
"app_key": app_key,
|
|
39
|
+
"being_draft": being_draft,
|
|
40
|
+
"items": result if include_raw else items,
|
|
41
|
+
"count": len(items),
|
|
42
|
+
"compact": not include_raw,
|
|
43
|
+
}
|
|
44
|
+
if include_raw:
|
|
45
|
+
response["summary"] = items
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
return self._run(profile, runner)
|
|
49
|
+
|
|
50
|
+
def custom_button_get(
|
|
51
|
+
self,
|
|
52
|
+
*,
|
|
53
|
+
profile: str,
|
|
54
|
+
app_key: str,
|
|
55
|
+
button_id: int,
|
|
56
|
+
being_draft: bool = True,
|
|
57
|
+
include_raw: bool = False,
|
|
58
|
+
) -> JSONObject:
|
|
59
|
+
self._require_app_key(app_key)
|
|
60
|
+
self._require_button_id(button_id)
|
|
61
|
+
|
|
62
|
+
def runner(session_profile, context):
|
|
63
|
+
params = {"beingDraft": being_draft}
|
|
64
|
+
result = self.backend.request("GET", context, f"/app/{app_key}/customButton/{button_id}", params=params)
|
|
65
|
+
response = {
|
|
66
|
+
"profile": profile,
|
|
67
|
+
"ws_id": session_profile.selected_ws_id,
|
|
68
|
+
"app_key": app_key,
|
|
69
|
+
"button_id": button_id,
|
|
70
|
+
"being_draft": being_draft,
|
|
71
|
+
"result": result if include_raw else self._compact_button_detail(result if isinstance(result, dict) else {}),
|
|
72
|
+
"compact": not include_raw,
|
|
73
|
+
}
|
|
74
|
+
if include_raw:
|
|
75
|
+
response["summary"] = self._compact_button_detail(result if isinstance(result, dict) else {})
|
|
76
|
+
return response
|
|
77
|
+
|
|
78
|
+
return self._run(profile, runner)
|
|
79
|
+
|
|
80
|
+
def custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
81
|
+
self._require_app_key(app_key)
|
|
82
|
+
body = self._require_dict(payload)
|
|
83
|
+
|
|
84
|
+
def runner(session_profile, context):
|
|
85
|
+
result = self.backend.request("POST", context, f"/app/{app_key}/customButton", json_body=deepcopy(body))
|
|
86
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
|
|
87
|
+
|
|
88
|
+
return self._run(profile, runner)
|
|
89
|
+
|
|
90
|
+
def custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
|
|
91
|
+
self._require_app_key(app_key)
|
|
92
|
+
self._require_button_id(button_id)
|
|
93
|
+
body = self._require_dict(payload)
|
|
94
|
+
|
|
95
|
+
def runner(session_profile, context):
|
|
96
|
+
result = self.backend.request(
|
|
97
|
+
"POST",
|
|
98
|
+
context,
|
|
99
|
+
f"/app/{app_key}/customButton/{button_id}",
|
|
100
|
+
json_body=deepcopy(body),
|
|
101
|
+
)
|
|
102
|
+
return self._attach_human_review_notice(
|
|
103
|
+
{
|
|
104
|
+
"profile": profile,
|
|
105
|
+
"ws_id": session_profile.selected_ws_id,
|
|
106
|
+
"app_key": app_key,
|
|
107
|
+
"button_id": button_id,
|
|
108
|
+
"result": result,
|
|
109
|
+
},
|
|
110
|
+
operation="update",
|
|
111
|
+
target="custom button configuration",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return self._run(profile, runner)
|
|
115
|
+
|
|
116
|
+
def custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
|
|
117
|
+
self._require_app_key(app_key)
|
|
118
|
+
self._require_button_id(button_id)
|
|
119
|
+
|
|
120
|
+
def runner(session_profile, context):
|
|
121
|
+
result = self.backend.request("DELETE", context, f"/app/{app_key}/customButton/{button_id}")
|
|
122
|
+
return self._attach_human_review_notice(
|
|
123
|
+
{
|
|
124
|
+
"profile": profile,
|
|
125
|
+
"ws_id": session_profile.selected_ws_id,
|
|
126
|
+
"app_key": app_key,
|
|
127
|
+
"button_id": button_id,
|
|
128
|
+
"result": result,
|
|
129
|
+
},
|
|
130
|
+
operation="delete",
|
|
131
|
+
target="custom button configuration",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return self._run(profile, runner)
|
|
135
|
+
|
|
136
|
+
def _require_app_key(self, app_key: str) -> None:
|
|
137
|
+
if not str(app_key or "").strip():
|
|
138
|
+
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
139
|
+
|
|
140
|
+
def _require_button_id(self, button_id: int) -> None:
|
|
141
|
+
if not isinstance(button_id, int) or isinstance(button_id, bool) or button_id <= 0:
|
|
142
|
+
raise_tool_error(QingflowApiError.config_error("button_id must be a positive integer"))
|
|
143
|
+
|
|
144
|
+
def _compact_button_base_info(self, item: dict[str, object]) -> JSONObject:
|
|
145
|
+
creator = item.get("creatorUserInfo") if isinstance(item.get("creatorUserInfo"), dict) else {}
|
|
146
|
+
return {
|
|
147
|
+
"button_id": item.get("buttonId"),
|
|
148
|
+
"button_text": item.get("buttonText"),
|
|
149
|
+
"button_icon": item.get("buttonIcon"),
|
|
150
|
+
"background_color": item.get("backgroundColor"),
|
|
151
|
+
"text_color": item.get("textColor"),
|
|
152
|
+
"creator_user_info": {
|
|
153
|
+
"uid": creator.get("uid"),
|
|
154
|
+
"name": creator.get("name"),
|
|
155
|
+
"email": creator.get("email"),
|
|
156
|
+
}
|
|
157
|
+
if creator
|
|
158
|
+
else None,
|
|
159
|
+
"used_in_chart_count": item.get("userInChartCount"),
|
|
160
|
+
"being_effective_external_qrobot": item.get("beingEffectiveExternalQRobot"),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def _compact_button_detail(self, item: dict[str, object]) -> JSONObject:
|
|
164
|
+
return {
|
|
165
|
+
"button_id": item.get("buttonId"),
|
|
166
|
+
"button_text": item.get("buttonText"),
|
|
167
|
+
"button_icon": item.get("buttonIcon"),
|
|
168
|
+
"background_color": item.get("backgroundColor"),
|
|
169
|
+
"text_color": item.get("textColor"),
|
|
170
|
+
"trigger_action": item.get("triggerAction"),
|
|
171
|
+
"trigger_link_url": item.get("triggerLinkUrl"),
|
|
172
|
+
"trigger_add_data_config": deepcopy(item.get("triggerAddDataConfig")) if isinstance(item.get("triggerAddDataConfig"), dict) else None,
|
|
173
|
+
"external_qrobot_config": deepcopy(item.get("customButtonExternalQRobotRelationVO"))
|
|
174
|
+
if isinstance(item.get("customButtonExternalQRobotRelationVO"), dict)
|
|
175
|
+
else None,
|
|
176
|
+
"trigger_wings_config": deepcopy(item.get("triggerWingsConfig")) if isinstance(item.get("triggerWingsConfig"), dict) else None,
|
|
177
|
+
}
|