@qingflow-tech/qingflow-app-user-mcp 1.0.2 → 1.0.3
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/docs/local-agent-install.md +9 -3
- package/npm/lib/runtime.mjs +10 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +21 -12
- package/skills/qingflow-app-user/references/data-gotchas.md +1 -1
- package/skills/qingflow-app-user/references/public-surface-sync.md +70 -0
- package/skills/qingflow-app-user/references/record-patterns.md +1 -1
- package/skills/qingflow-record-analysis/SKILL.md +44 -2
- package/skills/qingflow-record-insert/SKILL.md +3 -0
- package/skills/qingflow-record-update/SKILL.md +3 -0
- package/skills/qingflow-task-ops/SKILL.md +31 -10
- package/src/qingflow_mcp/__init__.py +33 -1
- package/src/qingflow_mcp/builder_facade/models.py +14 -4
- package/src/qingflow_mcp/builder_facade/service.py +1582 -124
- package/src/qingflow_mcp/cli/commands/auth.py +63 -0
- package/src/qingflow_mcp/cli/commands/builder.py +4 -3
- package/src/qingflow_mcp/cli/commands/record.py +5 -5
- package/src/qingflow_mcp/cli/commands/task.py +74 -22
- package/src/qingflow_mcp/cli/commands/workspace.py +22 -0
- package/src/qingflow_mcp/cli/formatters.py +287 -48
- package/src/qingflow_mcp/cli/main.py +6 -1
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/config.py +1 -1
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/public_surface.py +11 -1
- package/src/qingflow_mcp/response_trim.py +380 -9
- package/src/qingflow_mcp/server.py +4 -0
- package/src/qingflow_mcp/server_app_builder.py +11 -1
- package/src/qingflow_mcp/server_app_user.py +24 -0
- package/src/qingflow_mcp/session_store.py +69 -15
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
- package/src/qingflow_mcp/solution/executor.py +2 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +48 -18
- package/src/qingflow_mcp/tools/app_tools.py +1 -0
- package/src/qingflow_mcp/tools/auth_tools.py +217 -9
- package/src/qingflow_mcp/tools/base.py +6 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +2 -2
- package/src/qingflow_mcp/tools/import_tools.py +36 -2
- package/src/qingflow_mcp/tools/record_tools.py +410 -156
- package/src/qingflow_mcp/tools/resource_read_tools.py +114 -32
- package/src/qingflow_mcp/tools/task_context_tools.py +899 -141
- package/src/qingflow_mcp/tools/workspace_tools.py +141 -0
|
@@ -35,6 +35,26 @@ class WorkspaceTools(ToolBase):
|
|
|
35
35
|
include_external=include_external,
|
|
36
36
|
)
|
|
37
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
|
+
|
|
38
58
|
@mcp.tool()
|
|
39
59
|
def workspace_set_plugin_status(
|
|
40
60
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -93,6 +113,68 @@ class WorkspaceTools(ToolBase):
|
|
|
93
113
|
|
|
94
114
|
return self._run(profile, runner, require_workspace=False)
|
|
95
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
|
+
|
|
96
178
|
@tool_cn_name("设置工作区插件状态")
|
|
97
179
|
def workspace_set_plugin_status(
|
|
98
180
|
self,
|
|
@@ -123,3 +205,62 @@ class WorkspaceTools(ToolBase):
|
|
|
123
205
|
}
|
|
124
206
|
|
|
125
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
|