@josephyan/qingflow-cli 0.2.0-beta.73 → 0.2.0-beta.75
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/backend_client.py +102 -0
- package/src/qingflow_mcp/builder_facade/models.py +121 -0
- package/src/qingflow_mcp/builder_facade/service.py +906 -71
- package/src/qingflow_mcp/cli/commands/builder.py +33 -1
- package/src/qingflow_mcp/cli/commands/repo.py +80 -0
- package/src/qingflow_mcp/cli/commands/task.py +17 -0
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/config.py +147 -0
- package/src/qingflow_mcp/public_surface.py +231 -0
- package/src/qingflow_mcp/repository_store.py +71 -0
- package/src/qingflow_mcp/response_trim.py +17 -246
- package/src/qingflow_mcp/server_app_builder.py +26 -1
- package/src/qingflow_mcp/server_app_user.py +5 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +145 -5
- package/src/qingflow_mcp/tools/record_tools.py +298 -25
- package/src/qingflow_mcp/tools/repository_dev_tools.py +533 -0
- package/src/qingflow_mcp/tools/task_context_tools.py +146 -7
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .config import get_repository_metadata_dir
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _utc_now() -> str:
|
|
13
|
+
return datetime.now(timezone.utc).isoformat()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _safe_key(value: str) -> str:
|
|
17
|
+
keep: list[str] = []
|
|
18
|
+
for char in value:
|
|
19
|
+
if char.isalnum() or char in {"-", "_"}:
|
|
20
|
+
keep.append(char)
|
|
21
|
+
else:
|
|
22
|
+
keep.append("_")
|
|
23
|
+
normalized = "".join(keep).strip("_")
|
|
24
|
+
return normalized or "repository"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class RepositoryMetadataStore:
|
|
29
|
+
base_dir: Path | None = None
|
|
30
|
+
_dir: Path = field(init=False, repr=False)
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
self._dir = self.base_dir or get_repository_metadata_dir()
|
|
34
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def put(self, repo_name: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
37
|
+
now = _utc_now()
|
|
38
|
+
existing = self.get(repo_name) or {}
|
|
39
|
+
data = dict(existing)
|
|
40
|
+
data.update(payload)
|
|
41
|
+
data["repo_name"] = repo_name
|
|
42
|
+
data["created_at"] = existing.get("created_at") or now
|
|
43
|
+
data["updated_at"] = now
|
|
44
|
+
self._path(repo_name).write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
45
|
+
return data
|
|
46
|
+
|
|
47
|
+
def get(self, repo_name: str) -> dict[str, Any] | None:
|
|
48
|
+
path = self._path(repo_name)
|
|
49
|
+
if not path.exists():
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
53
|
+
except (OSError, json.JSONDecodeError):
|
|
54
|
+
path.unlink(missing_ok=True)
|
|
55
|
+
return None
|
|
56
|
+
return payload if isinstance(payload, dict) else None
|
|
57
|
+
|
|
58
|
+
def list(self) -> list[dict[str, Any]]:
|
|
59
|
+
entries: list[dict[str, Any]] = []
|
|
60
|
+
for path in sorted(self._dir.glob("*.json")):
|
|
61
|
+
try:
|
|
62
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, json.JSONDecodeError):
|
|
64
|
+
continue
|
|
65
|
+
if isinstance(payload, dict):
|
|
66
|
+
entries.append(payload)
|
|
67
|
+
entries.sort(key=lambda item: str(item.get("updated_at") or ""), reverse=True)
|
|
68
|
+
return entries
|
|
69
|
+
|
|
70
|
+
def _path(self, repo_name: str) -> Path:
|
|
71
|
+
return self._dir / f"{_safe_key(repo_name)}.json"
|
|
@@ -5,11 +5,17 @@ from copy import deepcopy
|
|
|
5
5
|
from functools import wraps
|
|
6
6
|
from typing import Any, Callable
|
|
7
7
|
|
|
8
|
+
from .public_surface import (
|
|
9
|
+
BUILDER_DOMAIN,
|
|
10
|
+
USER_DOMAIN,
|
|
11
|
+
cli_trim_key_from_namespace,
|
|
12
|
+
server_method_map,
|
|
13
|
+
tool_key,
|
|
14
|
+
)
|
|
15
|
+
|
|
8
16
|
|
|
9
17
|
JSONObject = dict[str, Any]
|
|
10
18
|
TransformFn = Callable[[JSONObject], None]
|
|
11
|
-
USER_DOMAIN = "user"
|
|
12
|
-
BUILDER_DOMAIN = "builder"
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
COMMON_SUCCESS_DROP_TOP = {
|
|
@@ -47,10 +53,6 @@ COMMON_ERROR_DROP_TOP = {
|
|
|
47
53
|
SUCCESS_POLICY_BY_TOOL: dict[str, TransformFn] = {}
|
|
48
54
|
|
|
49
55
|
|
|
50
|
-
def _tool_key(domain: str, tool_name: str) -> str:
|
|
51
|
-
return f"{domain}:{tool_name}"
|
|
52
|
-
|
|
53
|
-
|
|
54
56
|
def trim_public_response(tool_name: str | None, payload: dict[str, Any]) -> dict[str, Any]:
|
|
55
57
|
if not isinstance(payload, dict):
|
|
56
58
|
return payload
|
|
@@ -100,246 +102,11 @@ def wrap_trimmed_methods(instance: object, method_map: dict[str, str]) -> object
|
|
|
100
102
|
|
|
101
103
|
|
|
102
104
|
def resolve_cli_tool_name(args: Any) -> str | None:
|
|
103
|
-
|
|
104
|
-
if command == "auth":
|
|
105
|
-
return {
|
|
106
|
-
"login": _tool_key(USER_DOMAIN, "auth_login"),
|
|
107
|
-
"use-token": _tool_key(USER_DOMAIN, "auth_use_token"),
|
|
108
|
-
"whoami": _tool_key(USER_DOMAIN, "auth_whoami"),
|
|
109
|
-
"logout": _tool_key(USER_DOMAIN, "auth_logout"),
|
|
110
|
-
}.get(getattr(args, "auth_command", None))
|
|
111
|
-
if command == "workspace":
|
|
112
|
-
return {
|
|
113
|
-
"list": _tool_key(USER_DOMAIN, "workspace_list"),
|
|
114
|
-
"select": _tool_key(USER_DOMAIN, "workspace_select"),
|
|
115
|
-
}.get(getattr(args, "workspace_command", None))
|
|
116
|
-
if command == "app":
|
|
117
|
-
return {
|
|
118
|
-
"list": _tool_key(USER_DOMAIN, "app_list"),
|
|
119
|
-
"search": _tool_key(USER_DOMAIN, "app_search"),
|
|
120
|
-
"get": _tool_key(USER_DOMAIN, "app_get"),
|
|
121
|
-
}.get(getattr(args, "app_command", None))
|
|
122
|
-
if command == "portal":
|
|
123
|
-
return {
|
|
124
|
-
"list": _tool_key(USER_DOMAIN, "portal_list"),
|
|
125
|
-
"get": _tool_key(USER_DOMAIN, "portal_get"),
|
|
126
|
-
}.get(getattr(args, "portal_command", None))
|
|
127
|
-
if command == "view":
|
|
128
|
-
return {
|
|
129
|
-
"get": _tool_key(USER_DOMAIN, "view_get"),
|
|
130
|
-
}.get(getattr(args, "view_command", None))
|
|
131
|
-
if command == "chart":
|
|
132
|
-
return {
|
|
133
|
-
"get": _tool_key(USER_DOMAIN, "chart_get"),
|
|
134
|
-
}.get(getattr(args, "chart_command", None))
|
|
135
|
-
if command == "record":
|
|
136
|
-
record_command = getattr(args, "record_command", None)
|
|
137
|
-
if record_command == "schema":
|
|
138
|
-
return {
|
|
139
|
-
"applicant": _tool_key(USER_DOMAIN, "record_schema_get"),
|
|
140
|
-
"browse": _tool_key(USER_DOMAIN, "record_browse_schema_get"),
|
|
141
|
-
"insert": _tool_key(USER_DOMAIN, "record_insert_schema_get"),
|
|
142
|
-
"update": _tool_key(USER_DOMAIN, "record_update_schema_get"),
|
|
143
|
-
"import": _tool_key(USER_DOMAIN, "record_import_schema_get"),
|
|
144
|
-
"code-block": _tool_key(USER_DOMAIN, "record_code_block_schema_get"),
|
|
145
|
-
}.get(getattr(args, "record_schema_command", None))
|
|
146
|
-
return {
|
|
147
|
-
"list": _tool_key(USER_DOMAIN, "record_list"),
|
|
148
|
-
"get": _tool_key(USER_DOMAIN, "record_get"),
|
|
149
|
-
"insert": _tool_key(USER_DOMAIN, "record_insert"),
|
|
150
|
-
"update": _tool_key(USER_DOMAIN, "record_update"),
|
|
151
|
-
"delete": _tool_key(USER_DOMAIN, "record_delete"),
|
|
152
|
-
"analyze": _tool_key(USER_DOMAIN, "record_analyze"),
|
|
153
|
-
"code-block-run": _tool_key(USER_DOMAIN, "record_code_block_run"),
|
|
154
|
-
}.get(record_command)
|
|
155
|
-
if command == "import":
|
|
156
|
-
return {
|
|
157
|
-
"template": _tool_key(USER_DOMAIN, "record_import_template_get"),
|
|
158
|
-
"verify": _tool_key(USER_DOMAIN, "record_import_verify"),
|
|
159
|
-
"repair": _tool_key(USER_DOMAIN, "record_import_repair_local"),
|
|
160
|
-
"start": _tool_key(USER_DOMAIN, "record_import_start"),
|
|
161
|
-
"status": _tool_key(USER_DOMAIN, "record_import_status_get"),
|
|
162
|
-
}.get(getattr(args, "import_command", None))
|
|
163
|
-
if command == "task":
|
|
164
|
-
return {
|
|
165
|
-
"list": _tool_key(USER_DOMAIN, "task_list"),
|
|
166
|
-
"get": _tool_key(USER_DOMAIN, "task_get"),
|
|
167
|
-
"action": _tool_key(USER_DOMAIN, "task_action_execute"),
|
|
168
|
-
"log": _tool_key(USER_DOMAIN, "task_workflow_log_get"),
|
|
169
|
-
}.get(getattr(args, "task_command", None))
|
|
170
|
-
if command not in {"builder", "build"}:
|
|
171
|
-
return None
|
|
172
|
-
builder_command = getattr(args, "builder_command", None)
|
|
173
|
-
if builder_command == "file":
|
|
174
|
-
return {
|
|
175
|
-
"upload-local": _tool_key(BUILDER_DOMAIN, "file_upload_local"),
|
|
176
|
-
}.get(getattr(args, "builder_file_command", None))
|
|
177
|
-
if builder_command == "feedback":
|
|
178
|
-
return {
|
|
179
|
-
"submit": _tool_key(BUILDER_DOMAIN, "feedback_submit"),
|
|
180
|
-
}.get(getattr(args, "builder_feedback_command", None))
|
|
181
|
-
if builder_command == "contract":
|
|
182
|
-
return _tool_key(BUILDER_DOMAIN, "builder_tool_contract")
|
|
183
|
-
if builder_command == "member":
|
|
184
|
-
return {
|
|
185
|
-
"search": _tool_key(BUILDER_DOMAIN, "member_search"),
|
|
186
|
-
}.get(getattr(args, "builder_member_command", None))
|
|
187
|
-
if builder_command == "role":
|
|
188
|
-
return {
|
|
189
|
-
"search": _tool_key(BUILDER_DOMAIN, "role_search"),
|
|
190
|
-
"create": _tool_key(BUILDER_DOMAIN, "role_create"),
|
|
191
|
-
}.get(getattr(args, "builder_role_command", None))
|
|
192
|
-
if builder_command == "package":
|
|
193
|
-
return {
|
|
194
|
-
"list": _tool_key(BUILDER_DOMAIN, "package_list"),
|
|
195
|
-
"resolve": _tool_key(BUILDER_DOMAIN, "package_resolve"),
|
|
196
|
-
"create": _tool_key(BUILDER_DOMAIN, "package_create"),
|
|
197
|
-
"attach-app": _tool_key(BUILDER_DOMAIN, "package_attach_app"),
|
|
198
|
-
}.get(getattr(args, "builder_package_command", None))
|
|
199
|
-
if builder_command == "app":
|
|
200
|
-
app_command = getattr(args, "builder_app_command", None)
|
|
201
|
-
if app_command == "get":
|
|
202
|
-
return {
|
|
203
|
-
"summary": _tool_key(BUILDER_DOMAIN, "app_get"),
|
|
204
|
-
"fields": _tool_key(BUILDER_DOMAIN, "app_get_fields"),
|
|
205
|
-
"layout": _tool_key(BUILDER_DOMAIN, "app_get_layout"),
|
|
206
|
-
"views": _tool_key(BUILDER_DOMAIN, "app_get_views"),
|
|
207
|
-
"flow": _tool_key(BUILDER_DOMAIN, "app_get_flow"),
|
|
208
|
-
"charts": _tool_key(BUILDER_DOMAIN, "app_get_charts"),
|
|
209
|
-
}.get(getattr(args, "builder_app_get_section", "summary"))
|
|
210
|
-
return {
|
|
211
|
-
"resolve": _tool_key(BUILDER_DOMAIN, "app_resolve"),
|
|
212
|
-
"release-edit-lock-if-mine": _tool_key(BUILDER_DOMAIN, "app_release_edit_lock_if_mine"),
|
|
213
|
-
}.get(app_command)
|
|
214
|
-
if builder_command == "button":
|
|
215
|
-
return {
|
|
216
|
-
"list": _tool_key(BUILDER_DOMAIN, "app_custom_button_list"),
|
|
217
|
-
"get": _tool_key(BUILDER_DOMAIN, "app_custom_button_get"),
|
|
218
|
-
"create": _tool_key(BUILDER_DOMAIN, "app_custom_button_create"),
|
|
219
|
-
"update": _tool_key(BUILDER_DOMAIN, "app_custom_button_update"),
|
|
220
|
-
"delete": _tool_key(BUILDER_DOMAIN, "app_custom_button_delete"),
|
|
221
|
-
}.get(getattr(args, "builder_button_command", None))
|
|
222
|
-
if builder_command == "portal":
|
|
223
|
-
return {
|
|
224
|
-
"list": _tool_key(BUILDER_DOMAIN, "portal_list"),
|
|
225
|
-
"get": _tool_key(BUILDER_DOMAIN, "portal_get"),
|
|
226
|
-
"apply": _tool_key(BUILDER_DOMAIN, "portal_apply"),
|
|
227
|
-
}.get(getattr(args, "builder_portal_command", None))
|
|
228
|
-
if builder_command == "view":
|
|
229
|
-
return {
|
|
230
|
-
"get": _tool_key(BUILDER_DOMAIN, "view_get"),
|
|
231
|
-
}.get(getattr(args, "builder_view_command", None))
|
|
232
|
-
if builder_command == "chart":
|
|
233
|
-
return {
|
|
234
|
-
"get": _tool_key(BUILDER_DOMAIN, "chart_get"),
|
|
235
|
-
}.get(getattr(args, "builder_chart_command", None))
|
|
236
|
-
if builder_command == "schema":
|
|
237
|
-
return {"apply": _tool_key(BUILDER_DOMAIN, "app_schema_apply")}.get(getattr(args, "builder_schema_command", None))
|
|
238
|
-
if builder_command == "layout":
|
|
239
|
-
return {"apply": _tool_key(BUILDER_DOMAIN, "app_layout_apply")}.get(getattr(args, "builder_layout_command", None))
|
|
240
|
-
if builder_command == "views":
|
|
241
|
-
return {"apply": _tool_key(BUILDER_DOMAIN, "app_views_apply")}.get(getattr(args, "builder_views_command", None))
|
|
242
|
-
if builder_command == "flow":
|
|
243
|
-
return {"apply": _tool_key(BUILDER_DOMAIN, "app_flow_apply")}.get(getattr(args, "builder_flow_command", None))
|
|
244
|
-
if builder_command == "charts":
|
|
245
|
-
return {"apply": _tool_key(BUILDER_DOMAIN, "app_charts_apply")}.get(getattr(args, "builder_charts_command", None))
|
|
246
|
-
if builder_command == "publish":
|
|
247
|
-
return {"verify": _tool_key(BUILDER_DOMAIN, "app_publish_verify")}.get(getattr(args, "builder_publish_command", None))
|
|
248
|
-
return None
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
USER_SERVER_METHOD_MAP = {
|
|
252
|
-
"auth_login": _tool_key(USER_DOMAIN, "auth_login"),
|
|
253
|
-
"auth_use_token": _tool_key(USER_DOMAIN, "auth_use_token"),
|
|
254
|
-
"auth_whoami": _tool_key(USER_DOMAIN, "auth_whoami"),
|
|
255
|
-
"auth_logout": _tool_key(USER_DOMAIN, "auth_logout"),
|
|
256
|
-
"workspace_list": _tool_key(USER_DOMAIN, "workspace_list"),
|
|
257
|
-
"workspace_select": _tool_key(USER_DOMAIN, "workspace_select"),
|
|
258
|
-
"app_list": _tool_key(USER_DOMAIN, "app_list"),
|
|
259
|
-
"app_search": _tool_key(USER_DOMAIN, "app_search"),
|
|
260
|
-
"app_get": _tool_key(USER_DOMAIN, "app_get"),
|
|
261
|
-
"portal_list": _tool_key(USER_DOMAIN, "portal_list"),
|
|
262
|
-
"portal_get": _tool_key(USER_DOMAIN, "portal_get"),
|
|
263
|
-
"view_get": _tool_key(USER_DOMAIN, "view_get"),
|
|
264
|
-
"chart_get": _tool_key(USER_DOMAIN, "chart_get"),
|
|
265
|
-
"file_get_upload_info": _tool_key(USER_DOMAIN, "file_get_upload_info"),
|
|
266
|
-
"file_upload_local": _tool_key(USER_DOMAIN, "file_upload_local"),
|
|
267
|
-
"feedback_submit": _tool_key(USER_DOMAIN, "feedback_submit"),
|
|
268
|
-
"record_import_schema_get": _tool_key(USER_DOMAIN, "record_import_schema_get"),
|
|
269
|
-
"record_import_template_get": _tool_key(USER_DOMAIN, "record_import_template_get"),
|
|
270
|
-
"record_import_verify": _tool_key(USER_DOMAIN, "record_import_verify"),
|
|
271
|
-
"record_import_repair_local": _tool_key(USER_DOMAIN, "record_import_repair_local"),
|
|
272
|
-
"record_import_start": _tool_key(USER_DOMAIN, "record_import_start"),
|
|
273
|
-
"record_import_status_get": _tool_key(USER_DOMAIN, "record_import_status_get"),
|
|
274
|
-
"record_insert_schema_get_public": _tool_key(USER_DOMAIN, "record_insert_schema_get"),
|
|
275
|
-
"record_member_candidates": _tool_key(USER_DOMAIN, "record_member_candidates"),
|
|
276
|
-
"record_department_candidates": _tool_key(USER_DOMAIN, "record_department_candidates"),
|
|
277
|
-
"record_analyze": _tool_key(USER_DOMAIN, "record_analyze"),
|
|
278
|
-
"record_list": _tool_key(USER_DOMAIN, "record_list"),
|
|
279
|
-
"record_get_public": _tool_key(USER_DOMAIN, "record_get"),
|
|
280
|
-
"record_browse_schema_get_public": _tool_key(USER_DOMAIN, "record_browse_schema_get"),
|
|
281
|
-
"record_update_schema_get_public": _tool_key(USER_DOMAIN, "record_update_schema_get"),
|
|
282
|
-
"record_insert_public": _tool_key(USER_DOMAIN, "record_insert"),
|
|
283
|
-
"record_update_public": _tool_key(USER_DOMAIN, "record_update"),
|
|
284
|
-
"record_delete_public": _tool_key(USER_DOMAIN, "record_delete"),
|
|
285
|
-
"record_code_block_schema_get_public": _tool_key(USER_DOMAIN, "record_code_block_schema_get"),
|
|
286
|
-
"record_code_block_run": _tool_key(USER_DOMAIN, "record_code_block_run"),
|
|
287
|
-
"task_list": _tool_key(USER_DOMAIN, "task_list"),
|
|
288
|
-
"task_get": _tool_key(USER_DOMAIN, "task_get"),
|
|
289
|
-
"task_action_execute": _tool_key(USER_DOMAIN, "task_action_execute"),
|
|
290
|
-
"task_associated_report_detail_get": _tool_key(USER_DOMAIN, "task_associated_report_detail_get"),
|
|
291
|
-
"task_workflow_log_get": _tool_key(USER_DOMAIN, "task_workflow_log_get"),
|
|
292
|
-
"directory_search": _tool_key(USER_DOMAIN, "directory_search"),
|
|
293
|
-
"directory_list_internal_users": _tool_key(USER_DOMAIN, "directory_list_internal_users"),
|
|
294
|
-
"directory_list_all_internal_users": _tool_key(USER_DOMAIN, "directory_list_all_internal_users"),
|
|
295
|
-
"directory_list_internal_departments": _tool_key(USER_DOMAIN, "directory_list_internal_departments"),
|
|
296
|
-
"directory_list_all_departments": _tool_key(USER_DOMAIN, "directory_list_all_departments"),
|
|
297
|
-
"directory_list_sub_departments": _tool_key(USER_DOMAIN, "directory_list_sub_departments"),
|
|
298
|
-
"directory_list_external_members": _tool_key(USER_DOMAIN, "directory_list_external_members"),
|
|
299
|
-
}
|
|
105
|
+
return cli_trim_key_from_namespace(args)
|
|
300
106
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"auth_whoami": _tool_key(BUILDER_DOMAIN, "auth_whoami"),
|
|
305
|
-
"auth_logout": _tool_key(BUILDER_DOMAIN, "auth_logout"),
|
|
306
|
-
"workspace_list": _tool_key(BUILDER_DOMAIN, "workspace_list"),
|
|
307
|
-
"workspace_select": _tool_key(BUILDER_DOMAIN, "workspace_select"),
|
|
308
|
-
"file_upload_local": _tool_key(BUILDER_DOMAIN, "file_upload_local"),
|
|
309
|
-
"feedback_submit": _tool_key(BUILDER_DOMAIN, "feedback_submit"),
|
|
310
|
-
"package_list": _tool_key(BUILDER_DOMAIN, "package_list"),
|
|
311
|
-
"package_resolve": _tool_key(BUILDER_DOMAIN, "package_resolve"),
|
|
312
|
-
"builder_tool_contract": _tool_key(BUILDER_DOMAIN, "builder_tool_contract"),
|
|
313
|
-
"package_create": _tool_key(BUILDER_DOMAIN, "package_create"),
|
|
314
|
-
"member_search": _tool_key(BUILDER_DOMAIN, "member_search"),
|
|
315
|
-
"role_search": _tool_key(BUILDER_DOMAIN, "role_search"),
|
|
316
|
-
"role_create": _tool_key(BUILDER_DOMAIN, "role_create"),
|
|
317
|
-
"package_attach_app": _tool_key(BUILDER_DOMAIN, "package_attach_app"),
|
|
318
|
-
"app_release_edit_lock_if_mine": _tool_key(BUILDER_DOMAIN, "app_release_edit_lock_if_mine"),
|
|
319
|
-
"app_resolve": _tool_key(BUILDER_DOMAIN, "app_resolve"),
|
|
320
|
-
"app_custom_button_list": _tool_key(BUILDER_DOMAIN, "app_custom_button_list"),
|
|
321
|
-
"app_custom_button_get": _tool_key(BUILDER_DOMAIN, "app_custom_button_get"),
|
|
322
|
-
"app_custom_button_create": _tool_key(BUILDER_DOMAIN, "app_custom_button_create"),
|
|
323
|
-
"app_custom_button_update": _tool_key(BUILDER_DOMAIN, "app_custom_button_update"),
|
|
324
|
-
"app_custom_button_delete": _tool_key(BUILDER_DOMAIN, "app_custom_button_delete"),
|
|
325
|
-
"app_get": _tool_key(BUILDER_DOMAIN, "app_get"),
|
|
326
|
-
"app_get_fields": _tool_key(BUILDER_DOMAIN, "app_get_fields"),
|
|
327
|
-
"app_get_layout": _tool_key(BUILDER_DOMAIN, "app_get_layout"),
|
|
328
|
-
"app_get_views": _tool_key(BUILDER_DOMAIN, "app_get_views"),
|
|
329
|
-
"app_get_flow": _tool_key(BUILDER_DOMAIN, "app_get_flow"),
|
|
330
|
-
"app_get_charts": _tool_key(BUILDER_DOMAIN, "app_get_charts"),
|
|
331
|
-
"portal_list": _tool_key(BUILDER_DOMAIN, "portal_list"),
|
|
332
|
-
"portal_get": _tool_key(BUILDER_DOMAIN, "portal_get"),
|
|
333
|
-
"view_get": _tool_key(BUILDER_DOMAIN, "view_get"),
|
|
334
|
-
"chart_get": _tool_key(BUILDER_DOMAIN, "chart_get"),
|
|
335
|
-
"app_schema_apply": _tool_key(BUILDER_DOMAIN, "app_schema_apply"),
|
|
336
|
-
"app_layout_apply": _tool_key(BUILDER_DOMAIN, "app_layout_apply"),
|
|
337
|
-
"app_flow_apply": _tool_key(BUILDER_DOMAIN, "app_flow_apply"),
|
|
338
|
-
"app_views_apply": _tool_key(BUILDER_DOMAIN, "app_views_apply"),
|
|
339
|
-
"app_charts_apply": _tool_key(BUILDER_DOMAIN, "app_charts_apply"),
|
|
340
|
-
"portal_apply": _tool_key(BUILDER_DOMAIN, "portal_apply"),
|
|
341
|
-
"app_publish_verify": _tool_key(BUILDER_DOMAIN, "app_publish_verify"),
|
|
342
|
-
}
|
|
107
|
+
|
|
108
|
+
USER_SERVER_METHOD_MAP = server_method_map(USER_DOMAIN)
|
|
109
|
+
BUILDER_SERVER_METHOD_MAP = server_method_map(BUILDER_DOMAIN)
|
|
343
110
|
|
|
344
111
|
|
|
345
112
|
def _wrap_callable(original: Callable[..., Any], tool_name: str) -> Callable[..., Any]:
|
|
@@ -600,7 +367,7 @@ def _trim_builder_list_like(payload: JSONObject) -> None:
|
|
|
600
367
|
def _register_policy(domains: tuple[str, ...], names: tuple[str, ...], transform: TransformFn) -> None:
|
|
601
368
|
for domain in domains:
|
|
602
369
|
for name in names:
|
|
603
|
-
SUCCESS_POLICY_BY_TOOL[
|
|
370
|
+
SUCCESS_POLICY_BY_TOOL[tool_key(domain, name)] = transform
|
|
604
371
|
|
|
605
372
|
|
|
606
373
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("auth_login", "auth_use_token", "auth_whoami"), _trim_auth_payload)
|
|
@@ -609,6 +376,7 @@ _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_list",), _trim_works
|
|
|
609
376
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_select",), _trim_workspace_select)
|
|
610
377
|
_register_policy((USER_DOMAIN,), ("app_list", "app_search"), _trim_app_search_like)
|
|
611
378
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("app_get",), _trim_app_get)
|
|
379
|
+
_register_policy((BUILDER_DOMAIN,), ("app_repair_code_blocks",), _trim_builder_list_like)
|
|
612
380
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("portal_list", "portal_get", "view_get", "chart_get"), _trim_builder_list_like)
|
|
613
381
|
_register_policy((USER_DOMAIN,), ("file_get_upload_info",), _trim_file_upload_info)
|
|
614
382
|
_register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("file_upload_local",), _trim_file_upload_local)
|
|
@@ -645,6 +413,7 @@ _register_policy(
|
|
|
645
413
|
(USER_DOMAIN,),
|
|
646
414
|
(
|
|
647
415
|
"task_get",
|
|
416
|
+
"task_save_only",
|
|
648
417
|
"task_action_execute",
|
|
649
418
|
"task_associated_report_detail_get",
|
|
650
419
|
"task_workflow_log_get",
|
|
@@ -680,6 +449,7 @@ _register_policy(
|
|
|
680
449
|
"package_list",
|
|
681
450
|
"package_resolve",
|
|
682
451
|
"builder_tool_contract",
|
|
452
|
+
"solution_install",
|
|
683
453
|
"package_create",
|
|
684
454
|
"member_search",
|
|
685
455
|
"role_search",
|
|
@@ -693,6 +463,7 @@ _register_policy(
|
|
|
693
463
|
"app_custom_button_update",
|
|
694
464
|
"app_custom_button_delete",
|
|
695
465
|
"app_get_fields",
|
|
466
|
+
"app_repair_code_blocks",
|
|
696
467
|
"app_get_layout",
|
|
697
468
|
"app_get_views",
|
|
698
469
|
"app_get_flow",
|
|
@@ -34,10 +34,12 @@ def build_builder_server() -> FastMCP:
|
|
|
34
34
|
"`feedback_submit` is always available as a cross-cutting helper when the current capability is unsupported, awkward, or still cannot satisfy the user's need after reasonable use; it does not require Qingflow login or workspace selection, and it should be called only after explicit user confirmation. "
|
|
35
35
|
"Follow the resource path resolve -> summary read -> apply -> attach -> publish_verify. "
|
|
36
36
|
"Use builder_tool_contract when you need a machine-readable contract, aliases, allowed enums, or a minimal valid example for a public builder tool. "
|
|
37
|
+
"Use solution_install when the user explicitly wants to install a packaged solution/template by solution_key, optionally copying bundled demo data. "
|
|
37
38
|
"If creating a new package may be appropriate, ask the user to confirm package creation before calling package_create; otherwise use package_resolve/package_list and app_resolve to locate resources, "
|
|
38
|
-
"app_get/app_get_fields/app_get_layout/app_get_views/app_get_flow/app_get_charts/portal_list/portal_get/view_get/chart_get for configuration reads, "
|
|
39
|
+
"app_get/app_get_fields/app_repair_code_blocks/app_get_layout/app_get_views/app_get_flow/app_get_charts/portal_list/portal_get/view_get/chart_get for configuration reads, "
|
|
39
40
|
"member_search/role_search/role_create when workflow assignees must come from the directory or role catalog, preferring roles over explicit members unless the user explicitly names members, "
|
|
40
41
|
"then app_schema_apply/app_layout_apply/app_flow_apply/app_views_apply/app_charts_apply/portal_apply to execute normalized patches; these apply tools perform planning, normalization, and dependency checks internally where applicable. Schema/layout/views noop requests skip publish, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates are replace-only and publish=false only guarantees draft/base-info updates, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
42
|
+
"For code_block fields with output bindings, always use qf_output assignment rather than const/let qf_output, and use app_repair_code_blocks when an existing form hangs because output-bound fields stay loading. "
|
|
41
43
|
"Use package_attach_app to attach apps to packages, and app_publish_verify for explicit final publish verification. "
|
|
42
44
|
"For workflow edits, keep the public builder surface on stable linear flows only: start/approve/fill/copy/webhook/end. Branch and condition nodes are intentionally disabled because the backend workflow route is not front-end stable for those node types. Declare node assignees and editable fields explicitly. "
|
|
43
45
|
"If builder writes are blocked by the current user's own edit lock, use app_release_edit_lock_if_mine with the lock owner details from the failed result. "
|
|
@@ -198,6 +200,20 @@ def build_builder_server() -> FastMCP:
|
|
|
198
200
|
) -> dict:
|
|
199
201
|
return ai_builder.package_create(profile=profile, package_name=package_name, icon=icon, color=color)
|
|
200
202
|
|
|
203
|
+
@server.tool()
|
|
204
|
+
def solution_install(
|
|
205
|
+
profile: str = DEFAULT_PROFILE,
|
|
206
|
+
solution_key: str = "",
|
|
207
|
+
being_copy_data: bool = True,
|
|
208
|
+
solution_source: str = "solutionDetail",
|
|
209
|
+
) -> dict:
|
|
210
|
+
return ai_builder.solution_install(
|
|
211
|
+
profile=profile,
|
|
212
|
+
solution_key=solution_key,
|
|
213
|
+
being_copy_data=being_copy_data,
|
|
214
|
+
solution_source=solution_source,
|
|
215
|
+
)
|
|
216
|
+
|
|
201
217
|
@server.tool()
|
|
202
218
|
def member_search(
|
|
203
219
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -318,6 +334,15 @@ def build_builder_server() -> FastMCP:
|
|
|
318
334
|
def app_get_fields(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|
|
319
335
|
return ai_builder.app_get_fields(profile=profile, app_key=app_key)
|
|
320
336
|
|
|
337
|
+
@server.tool()
|
|
338
|
+
def app_repair_code_blocks(
|
|
339
|
+
profile: str = DEFAULT_PROFILE,
|
|
340
|
+
app_key: str = "",
|
|
341
|
+
field: str | None = None,
|
|
342
|
+
apply: bool = False,
|
|
343
|
+
) -> dict:
|
|
344
|
+
return ai_builder.app_repair_code_blocks(profile=profile, app_key=app_key, field=field, apply=apply)
|
|
345
|
+
|
|
321
346
|
@server.tool()
|
|
322
347
|
def app_get_layout(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|
|
323
348
|
return ai_builder.app_get_layout(profile=profile, app_key=app_key)
|
|
@@ -117,6 +117,7 @@ Analysis answers must include concrete numbers. When applicable, include percent
|
|
|
117
117
|
|
|
118
118
|
- Read relation targets from `record_insert_schema_get` / `record_update_schema_get` relation metadata before preparing relation writes.
|
|
119
119
|
- Member and department fields may be written with natural strings directly on `record_insert` / `record_update`; only fall back to `record_member_candidates` or `record_department_candidates` when the user wants explicit candidate browsing or the write returns ambiguity that needs confirmation.
|
|
120
|
+
- When candidate browsing must match a real update/write scope, pass `record_id`, `workflow_node_id`, and any pending `fields` context to the candidate tool; otherwise the candidate result is only a static applicant-node preview.
|
|
120
121
|
- If explicit candidate browsing is needed for default-all member or department fields, prefer those field candidate tools instead of starting with `directory_*`.
|
|
121
122
|
|
|
122
123
|
## Code Block Path
|
|
@@ -143,11 +144,14 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
143
144
|
|
|
144
145
|
## Task Workflow Path
|
|
145
146
|
|
|
146
|
-
`task_list -> task_get -> task_action_execute`
|
|
147
|
+
`task_list -> task_get -> task_save_only / task_action_execute`
|
|
147
148
|
|
|
148
149
|
- Use `task_associated_report_detail_get` for associated view or report details.
|
|
149
150
|
- Use `task_workflow_log_get` for full workflow log history.
|
|
150
151
|
- Task actions operate on `app_key + record_id + workflow_node_id`, not `task_id`.
|
|
152
|
+
- Treat `task_action_execute` as the tool-level action enum surface; the current task's real actions are only the ones listed in `task_get.capabilities.available_actions`.
|
|
153
|
+
- Use `task_save_only` when the user wants to save editable field changes on the current node without advancing the workflow.
|
|
154
|
+
- `save_only` is exposed only when the backend current-node `editableQueIds` signal returns a non-empty result; MCP no longer infers `save_only` from local schema reconstruction.
|
|
151
155
|
|
|
152
156
|
## Time Handling
|
|
153
157
|
|