@josephyan/qingflow-app-builder-mcp 0.1.0-beta.9

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.
Files changed (55) hide show
  1. package/README.md +21 -0
  2. package/docs/local-agent-install.md +228 -0
  3. package/entry_point.py +13 -0
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +7 -0
  5. package/npm/lib/runtime.mjs +146 -0
  6. package/npm/scripts/postinstall.mjs +12 -0
  7. package/package.json +33 -0
  8. package/pyproject.toml +64 -0
  9. package/qingflow-app-builder-mcp +15 -0
  10. package/src/qingflow_mcp/__init__.py +5 -0
  11. package/src/qingflow_mcp/__main__.py +5 -0
  12. package/src/qingflow_mcp/backend_client.py +336 -0
  13. package/src/qingflow_mcp/config.py +182 -0
  14. package/src/qingflow_mcp/errors.py +66 -0
  15. package/src/qingflow_mcp/json_types.py +18 -0
  16. package/src/qingflow_mcp/list_type_labels.py +52 -0
  17. package/src/qingflow_mcp/server.py +70 -0
  18. package/src/qingflow_mcp/server_app_builder.py +352 -0
  19. package/src/qingflow_mcp/server_app_user.py +334 -0
  20. package/src/qingflow_mcp/session_store.py +249 -0
  21. package/src/qingflow_mcp/solution/__init__.py +6 -0
  22. package/src/qingflow_mcp/solution/build_assembly_store.py +137 -0
  23. package/src/qingflow_mcp/solution/compiler/__init__.py +265 -0
  24. package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
  25. package/src/qingflow_mcp/solution/compiler/form_compiler.py +456 -0
  26. package/src/qingflow_mcp/solution/compiler/icon_utils.py +113 -0
  27. package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
  28. package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
  29. package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
  30. package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
  31. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +134 -0
  32. package/src/qingflow_mcp/solution/design_session.py +222 -0
  33. package/src/qingflow_mcp/solution/design_store.py +100 -0
  34. package/src/qingflow_mcp/solution/executor.py +2065 -0
  35. package/src/qingflow_mcp/solution/normalizer.py +23 -0
  36. package/src/qingflow_mcp/solution/run_store.py +221 -0
  37. package/src/qingflow_mcp/solution/spec_models.py +853 -0
  38. package/src/qingflow_mcp/tools/__init__.py +1 -0
  39. package/src/qingflow_mcp/tools/app_tools.py +406 -0
  40. package/src/qingflow_mcp/tools/approval_tools.py +498 -0
  41. package/src/qingflow_mcp/tools/auth_tools.py +514 -0
  42. package/src/qingflow_mcp/tools/base.py +81 -0
  43. package/src/qingflow_mcp/tools/directory_tools.py +476 -0
  44. package/src/qingflow_mcp/tools/file_tools.py +375 -0
  45. package/src/qingflow_mcp/tools/navigation_tools.py +177 -0
  46. package/src/qingflow_mcp/tools/package_tools.py +142 -0
  47. package/src/qingflow_mcp/tools/portal_tools.py +100 -0
  48. package/src/qingflow_mcp/tools/qingbi_report_tools.py +235 -0
  49. package/src/qingflow_mcp/tools/record_tools.py +4307 -0
  50. package/src/qingflow_mcp/tools/role_tools.py +94 -0
  51. package/src/qingflow_mcp/tools/solution_tools.py +2680 -0
  52. package/src/qingflow_mcp/tools/task_tools.py +692 -0
  53. package/src/qingflow_mcp/tools/view_tools.py +280 -0
  54. package/src/qingflow_mcp/tools/workflow_tools.py +238 -0
  55. package/src/qingflow_mcp/tools/workspace_tools.py +170 -0
@@ -0,0 +1,170 @@
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
11
+
12
+
13
+ class WorkspaceTools(ToolBase):
14
+ def register(self, mcp: FastMCP) -> None:
15
+ @mcp.tool()
16
+ def workspace_list(
17
+ profile: str = DEFAULT_PROFILE,
18
+ page_num: int = 1,
19
+ page_size: int = 20,
20
+ include_external: bool = False,
21
+ ) -> dict[str, Any]:
22
+ return self.workspace_list(
23
+ profile=profile,
24
+ page_num=page_num,
25
+ page_size=page_size,
26
+ include_external=include_external,
27
+ )
28
+
29
+ @mcp.tool()
30
+ def workspace_select(profile: str = DEFAULT_PROFILE, ws_id: int = 0) -> dict[str, Any]:
31
+ return self.workspace_select(profile=profile, ws_id=ws_id)
32
+
33
+ @mcp.tool()
34
+ def workspace_set_plugin_status(
35
+ profile: str = DEFAULT_PROFILE,
36
+ plugin_id: int = 0,
37
+ being_installed: bool = True,
38
+ ) -> dict[str, Any]:
39
+ return self.workspace_set_plugin_status(
40
+ profile=profile,
41
+ plugin_id=plugin_id,
42
+ being_installed=being_installed,
43
+ )
44
+
45
+ def workspace_list(
46
+ self,
47
+ *,
48
+ profile: str = DEFAULT_PROFILE,
49
+ page_num: int = 1,
50
+ page_size: int = 20,
51
+ include_external: bool = False,
52
+ ) -> dict[str, Any]:
53
+ if page_num <= 0 or page_size <= 0:
54
+ raise_tool_error(QingflowApiError.config_error("page_num and page_size must be positive"))
55
+
56
+ def runner(_, context):
57
+ path = "/user/allWorkspaceList/pageQuery" if include_external else "/user/workspaceList/pageQuery"
58
+ result = self.backend.request(
59
+ "POST",
60
+ context,
61
+ path,
62
+ json_body={"pageNum": page_num, "pageSize": page_size, "authList": [0, 1, 2]},
63
+ )
64
+ return {
65
+ "profile": profile,
66
+ "include_external": include_external,
67
+ "page": result,
68
+ }
69
+
70
+ return self._run(profile, runner, require_workspace=False)
71
+
72
+ def workspace_select(self, *, profile: str = DEFAULT_PROFILE, ws_id: int) -> dict[str, Any]:
73
+ if ws_id <= 0:
74
+ raise_tool_error(QingflowApiError.config_error("ws_id must be positive"))
75
+
76
+ def runner(_, context):
77
+ # Create a context with the target ws_id for the API call
78
+ # This is necessary because the API requires wsId header even when getting workspace details
79
+ call_context = BackendRequestContext(
80
+ base_url=context.base_url,
81
+ token=context.token,
82
+ ws_id=ws_id,
83
+ qf_version=context.qf_version,
84
+ qf_version_source=context.qf_version_source,
85
+ )
86
+
87
+ try:
88
+ result = self.backend.request("GET", call_context, f"/user/workspace/{ws_id}")
89
+ except QingflowApiError as e:
90
+ if e.http_status == 404:
91
+ # Fallback: try to find the workspace name from the list
92
+ try:
93
+ workspaces_data = self.backend.request("POST", call_context, "/user/workspaceList/pageQuery", json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2]})
94
+ workspaces = workspaces_data.get("list", []) if isinstance(workspaces_data, dict) else []
95
+ found = next((ws for ws in workspaces if ws.get("wsId") == ws_id), None)
96
+ if found:
97
+ result = found
98
+ else:
99
+ result = {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
100
+ except Exception:
101
+ result = {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
102
+ else:
103
+ raise
104
+
105
+ ws_name = result.get("workspaceName") or result.get("wsName") if isinstance(result, dict) else None
106
+ session_profile = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=ws_name)
107
+ workspace_qf_version = self._workspace_system_version(result)
108
+ if context.qf_version is None and workspace_qf_version is not None:
109
+ session_profile = self.sessions.update_route(
110
+ profile,
111
+ qf_version=workspace_qf_version,
112
+ qf_version_source="workspace_system_version",
113
+ )
114
+ return {
115
+ "profile": profile,
116
+ "selected_ws_id": session_profile.selected_ws_id,
117
+ "selected_ws_name": session_profile.selected_ws_name,
118
+ "workspace": result,
119
+ "qf_version": session_profile.qf_version,
120
+ "qf_version_source": session_profile.qf_version_source,
121
+ "request_route": self.backend.describe_route(
122
+ BackendRequestContext(
123
+ base_url=session_profile.base_url,
124
+ token=context.token,
125
+ ws_id=session_profile.selected_ws_id,
126
+ qf_version=session_profile.qf_version,
127
+ qf_version_source=session_profile.qf_version_source,
128
+ )
129
+ ),
130
+ }
131
+
132
+ return self._run(profile, runner, require_workspace=False)
133
+
134
+ def _workspace_system_version(self, workspace: Any) -> str | None:
135
+ if not isinstance(workspace, dict):
136
+ return None
137
+ value = workspace.get("systemVersion")
138
+ if value is None:
139
+ return None
140
+ normalized = str(value).strip()
141
+ return normalized or None
142
+
143
+ def workspace_set_plugin_status(
144
+ self,
145
+ *,
146
+ profile: str = DEFAULT_PROFILE,
147
+ plugin_id: int,
148
+ being_installed: bool = True,
149
+ ) -> dict[str, Any]:
150
+ if plugin_id <= 0:
151
+ raise_tool_error(QingflowApiError.config_error("plugin_id must be positive"))
152
+
153
+ def runner(_, context):
154
+ result = self.backend.request(
155
+ "POST",
156
+ context,
157
+ "/ws/plugin",
158
+ json_body={
159
+ "pluginId": plugin_id,
160
+ "beingInstalled": being_installed,
161
+ },
162
+ )
163
+ return {
164
+ "profile": profile,
165
+ "plugin_id": plugin_id,
166
+ "being_installed": being_installed,
167
+ "result": result,
168
+ }
169
+
170
+ return self._run(profile, runner)