@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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 +5 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-builder/SKILL.md +155 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +210 -0
- package/src/qingflow_mcp/builder_facade/models.py +1252 -3
- package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +515 -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/record.py +304 -0
- package/src/qingflow_mcp/cli/commands/task.py +89 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +355 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +149 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/response_trim.py +668 -0
- package/src/qingflow_mcp/server.py +160 -18
- package/src/qingflow_mcp/server_app_builder.py +275 -68
- package/src/qingflow_mcp/server_app_user.py +219 -191
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
- package/src/qingflow_mcp/tools/app_tools.py +419 -12
- package/src/qingflow_mcp/tools/approval_tools.py +571 -72
- package/src/qingflow_mcp/tools/auth_tools.py +398 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +2150 -0
- package/src/qingflow_mcp/tools/package_tools.py +18 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
- package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
|
@@ -105,21 +105,27 @@ class WorkspaceTools(ToolBase):
|
|
|
105
105
|
result = self.backend.request("GET", call_context, f"/user/workspace/{ws_id}")
|
|
106
106
|
except QingflowApiError as e:
|
|
107
107
|
if e.http_status == 404:
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
workspaces_data = self.backend.request("POST", call_context, "/user/workspaceList/pageQuery", json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2]})
|
|
111
|
-
workspaces = workspaces_data.get("list", []) if isinstance(workspaces_data, dict) else []
|
|
112
|
-
found = next((ws for ws in workspaces if ws.get("wsId") == ws_id), None)
|
|
113
|
-
if found:
|
|
114
|
-
result = found
|
|
115
|
-
else:
|
|
116
|
-
result = {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
|
|
117
|
-
except Exception:
|
|
118
|
-
result = {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
|
|
108
|
+
result = self._workspace_from_list(call_context, ws_id) or {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
|
|
119
109
|
else:
|
|
120
110
|
raise
|
|
121
111
|
|
|
122
|
-
|
|
112
|
+
if isinstance(result, dict):
|
|
113
|
+
ws_name = (
|
|
114
|
+
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
115
|
+
or None
|
|
116
|
+
)
|
|
117
|
+
if ws_name is None:
|
|
118
|
+
fallback_workspace = self._workspace_from_list(call_context, ws_id)
|
|
119
|
+
if isinstance(fallback_workspace, dict):
|
|
120
|
+
result = fallback_workspace
|
|
121
|
+
ws_name = (
|
|
122
|
+
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
123
|
+
or None
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
ws_name = None
|
|
127
|
+
if ws_name is None:
|
|
128
|
+
ws_name = f"Workspace {ws_id}"
|
|
123
129
|
session_profile = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=ws_name)
|
|
124
130
|
workspace_qf_version = self._workspace_system_version(result)
|
|
125
131
|
should_refresh_route = (
|
|
@@ -133,26 +139,54 @@ class WorkspaceTools(ToolBase):
|
|
|
133
139
|
qf_version=workspace_qf_version,
|
|
134
140
|
qf_version_source="workspace_system_version",
|
|
135
141
|
)
|
|
142
|
+
active_context = BackendRequestContext(
|
|
143
|
+
base_url=session_profile.base_url,
|
|
144
|
+
token=context.token,
|
|
145
|
+
ws_id=session_profile.selected_ws_id,
|
|
146
|
+
qf_version=session_profile.qf_version,
|
|
147
|
+
qf_version_source=session_profile.qf_version_source,
|
|
148
|
+
)
|
|
149
|
+
workspace_version = self._workspace_version_summary(
|
|
150
|
+
self._fetch_workspace_account_info(active_context),
|
|
151
|
+
workspace_base_info=self._fetch_workspace_base_info(active_context),
|
|
152
|
+
workspace_detail=result,
|
|
153
|
+
)
|
|
136
154
|
return {
|
|
137
155
|
"profile": profile,
|
|
138
156
|
"selected_ws_id": session_profile.selected_ws_id,
|
|
139
157
|
"selected_ws_name": session_profile.selected_ws_name,
|
|
140
158
|
"workspace": result,
|
|
159
|
+
"workspace_version": workspace_version,
|
|
141
160
|
"qf_version": session_profile.qf_version,
|
|
142
161
|
"qf_version_source": session_profile.qf_version_source,
|
|
143
162
|
"request_route": self.backend.describe_route(
|
|
144
|
-
|
|
145
|
-
base_url=session_profile.base_url,
|
|
146
|
-
token=context.token,
|
|
147
|
-
ws_id=session_profile.selected_ws_id,
|
|
148
|
-
qf_version=session_profile.qf_version,
|
|
149
|
-
qf_version_source=session_profile.qf_version_source,
|
|
150
|
-
)
|
|
163
|
+
active_context
|
|
151
164
|
),
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
return self._run(profile, runner, require_workspace=False)
|
|
155
168
|
|
|
169
|
+
def _workspace_from_list(self, context: BackendRequestContext, ws_id: int) -> dict[str, Any] | None:
|
|
170
|
+
try:
|
|
171
|
+
payload = self.backend.request(
|
|
172
|
+
"POST",
|
|
173
|
+
context,
|
|
174
|
+
"/user/workspaceList/pageQuery",
|
|
175
|
+
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2]},
|
|
176
|
+
)
|
|
177
|
+
except Exception:
|
|
178
|
+
return None
|
|
179
|
+
workspaces = payload.get("list", []) if isinstance(payload, dict) else []
|
|
180
|
+
found = next(
|
|
181
|
+
(
|
|
182
|
+
item
|
|
183
|
+
for item in workspaces
|
|
184
|
+
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
185
|
+
),
|
|
186
|
+
None,
|
|
187
|
+
)
|
|
188
|
+
return found if isinstance(found, dict) else None
|
|
189
|
+
|
|
156
190
|
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
157
191
|
if not isinstance(workspace, dict):
|
|
158
192
|
return None
|
|
@@ -162,6 +196,116 @@ class WorkspaceTools(ToolBase):
|
|
|
162
196
|
normalized = str(value).strip()
|
|
163
197
|
return normalized or None
|
|
164
198
|
|
|
199
|
+
def _fetch_workspace_base_info(self, context: BackendRequestContext) -> dict[str, Any] | None:
|
|
200
|
+
try:
|
|
201
|
+
payload = self.backend.request("GET", context, "/ws/baseInfo")
|
|
202
|
+
except QingflowApiError:
|
|
203
|
+
return None
|
|
204
|
+
return payload if isinstance(payload, dict) else None
|
|
205
|
+
|
|
206
|
+
def _fetch_workspace_account_info(self, context: BackendRequestContext) -> dict[str, Any] | None:
|
|
207
|
+
try:
|
|
208
|
+
payload = self.backend.request("GET", context, "/ws/account")
|
|
209
|
+
except QingflowApiError:
|
|
210
|
+
return None
|
|
211
|
+
return payload if isinstance(payload, dict) else None
|
|
212
|
+
|
|
213
|
+
def _workspace_version_summary(
|
|
214
|
+
self,
|
|
215
|
+
payload: Any,
|
|
216
|
+
*,
|
|
217
|
+
workspace_base_info: Any,
|
|
218
|
+
workspace_detail: Any,
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
account_info = payload if isinstance(payload, dict) else {}
|
|
221
|
+
base_info = workspace_base_info if isinstance(workspace_base_info, dict) else {}
|
|
222
|
+
detail_info = workspace_detail if isinstance(workspace_detail, dict) else {}
|
|
223
|
+
level_code = self._first_present_int(
|
|
224
|
+
account_info.get("accountLevel"),
|
|
225
|
+
base_info.get("accountLevel"),
|
|
226
|
+
detail_info.get("accountLevel"),
|
|
227
|
+
)
|
|
228
|
+
level_name = self._account_level_name(level_code)
|
|
229
|
+
return {
|
|
230
|
+
"level_code": level_code,
|
|
231
|
+
"level_name": level_name,
|
|
232
|
+
"display_name": self._account_level_display_name(level_name),
|
|
233
|
+
"being_trial": self._first_present_bool(
|
|
234
|
+
account_info.get("trial"),
|
|
235
|
+
base_info.get("trial"),
|
|
236
|
+
detail_info.get("trial"),
|
|
237
|
+
),
|
|
238
|
+
"expire_date": self._first_present_value(
|
|
239
|
+
account_info.get("expireDate"),
|
|
240
|
+
base_info.get("expireDate"),
|
|
241
|
+
detail_info.get("expireDate"),
|
|
242
|
+
),
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
def _account_level_name(self, level_code: int | None) -> str | None:
|
|
246
|
+
mapping = {
|
|
247
|
+
0: "FREE",
|
|
248
|
+
10: "AIR",
|
|
249
|
+
20: "BASIC",
|
|
250
|
+
30: "TEAM",
|
|
251
|
+
35: "PROFESSIONAL",
|
|
252
|
+
40: "ENTERPRISE",
|
|
253
|
+
}
|
|
254
|
+
return mapping.get(level_code)
|
|
255
|
+
|
|
256
|
+
def _account_level_display_name(self, level_name: str | None) -> str | None:
|
|
257
|
+
mapping = {
|
|
258
|
+
"FREE": "免费版",
|
|
259
|
+
"AIR": "Air版",
|
|
260
|
+
"BASIC": "Pro版",
|
|
261
|
+
"TEAM": "团队版",
|
|
262
|
+
"PROFESSIONAL": "专业版",
|
|
263
|
+
"ENTERPRISE": "企业版",
|
|
264
|
+
}
|
|
265
|
+
return mapping.get(level_name)
|
|
266
|
+
|
|
267
|
+
def _coerce_int(self, value: Any) -> int | None:
|
|
268
|
+
if isinstance(value, bool) or value is None:
|
|
269
|
+
return None
|
|
270
|
+
if isinstance(value, int):
|
|
271
|
+
return value
|
|
272
|
+
try:
|
|
273
|
+
return int(str(value).strip())
|
|
274
|
+
except (TypeError, ValueError):
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
def _coerce_bool(self, value: Any) -> bool | None:
|
|
278
|
+
if isinstance(value, bool):
|
|
279
|
+
return value
|
|
280
|
+
if value is None:
|
|
281
|
+
return None
|
|
282
|
+
normalized = str(value).strip().lower()
|
|
283
|
+
if normalized in {"true", "1"}:
|
|
284
|
+
return True
|
|
285
|
+
if normalized in {"false", "0"}:
|
|
286
|
+
return False
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
def _first_present_int(self, *values: Any) -> int | None:
|
|
290
|
+
for value in values:
|
|
291
|
+
coerced = self._coerce_int(value)
|
|
292
|
+
if coerced is not None:
|
|
293
|
+
return coerced
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
def _first_present_bool(self, *values: Any) -> bool | None:
|
|
297
|
+
for value in values:
|
|
298
|
+
coerced = self._coerce_bool(value)
|
|
299
|
+
if coerced is not None:
|
|
300
|
+
return coerced
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def _first_present_value(self, *values: Any) -> Any:
|
|
304
|
+
for value in values:
|
|
305
|
+
if value is not None:
|
|
306
|
+
return value
|
|
307
|
+
return None
|
|
308
|
+
|
|
165
309
|
def workspace_set_plugin_status(
|
|
166
310
|
self,
|
|
167
311
|
*,
|