@josephyan/qingflow-mcp 0.1.0-beta.2

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 (52) hide show
  1. package/README.md +517 -0
  2. package/docs/local-agent-install.md +213 -0
  3. package/entry_point.py +13 -0
  4. package/npm/bin/qingflow-mcp.mjs +7 -0
  5. package/npm/lib/runtime.mjs +146 -0
  6. package/npm/scripts/postinstall.mjs +12 -0
  7. package/package.json +34 -0
  8. package/pyproject.toml +63 -0
  9. package/qingflow-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 +166 -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/server.py +70 -0
  17. package/src/qingflow_mcp/session_store.py +235 -0
  18. package/src/qingflow_mcp/solution/__init__.py +6 -0
  19. package/src/qingflow_mcp/solution/build_assembly_store.py +137 -0
  20. package/src/qingflow_mcp/solution/compiler/__init__.py +265 -0
  21. package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
  22. package/src/qingflow_mcp/solution/compiler/form_compiler.py +456 -0
  23. package/src/qingflow_mcp/solution/compiler/icon_utils.py +113 -0
  24. package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
  25. package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
  26. package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
  27. package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
  28. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +134 -0
  29. package/src/qingflow_mcp/solution/design_session.py +222 -0
  30. package/src/qingflow_mcp/solution/design_store.py +100 -0
  31. package/src/qingflow_mcp/solution/executor.py +2064 -0
  32. package/src/qingflow_mcp/solution/normalizer.py +23 -0
  33. package/src/qingflow_mcp/solution/run_store.py +221 -0
  34. package/src/qingflow_mcp/solution/spec_models.py +755 -0
  35. package/src/qingflow_mcp/tools/__init__.py +1 -0
  36. package/src/qingflow_mcp/tools/app_tools.py +239 -0
  37. package/src/qingflow_mcp/tools/approval_tools.py +481 -0
  38. package/src/qingflow_mcp/tools/auth_tools.py +496 -0
  39. package/src/qingflow_mcp/tools/base.py +81 -0
  40. package/src/qingflow_mcp/tools/directory_tools.py +476 -0
  41. package/src/qingflow_mcp/tools/file_tools.py +375 -0
  42. package/src/qingflow_mcp/tools/navigation_tools.py +177 -0
  43. package/src/qingflow_mcp/tools/package_tools.py +142 -0
  44. package/src/qingflow_mcp/tools/portal_tools.py +100 -0
  45. package/src/qingflow_mcp/tools/qingbi_report_tools.py +258 -0
  46. package/src/qingflow_mcp/tools/record_tools.py +4305 -0
  47. package/src/qingflow_mcp/tools/role_tools.py +94 -0
  48. package/src/qingflow_mcp/tools/solution_tools.py +1860 -0
  49. package/src/qingflow_mcp/tools/task_tools.py +677 -0
  50. package/src/qingflow_mcp/tools/view_tools.py +324 -0
  51. package/src/qingflow_mcp/tools/workflow_tools.py +311 -0
  52. package/src/qingflow_mcp/tools/workspace_tools.py +143 -0
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,239 @@
1
+ from __future__ import annotations
2
+
3
+ from mcp.server.fastmcp import FastMCP
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 AppTools(ToolBase):
12
+ def register(self, mcp: FastMCP) -> None:
13
+ @mcp.tool()
14
+ def app_list(profile: str = DEFAULT_PROFILE, ship_auth: bool = False) -> JSONObject:
15
+ return self.app_list(profile=profile, ship_auth=ship_auth)
16
+
17
+ @mcp.tool()
18
+ def app_search(profile: str = DEFAULT_PROFILE, keyword: str = "", page_num: int = 1, page_size: int = 50) -> JSONObject:
19
+ return self.app_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
20
+
21
+ @mcp.tool()
22
+ def app_get_base(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
23
+ return self.app_get_base(profile=profile, app_key=app_key)
24
+
25
+ @mcp.tool(description=self._high_risk_tool_description(operation="update", target="app base settings"))
26
+ def app_update_base(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: JSONObject | None = None) -> JSONObject:
27
+ return self.app_update_base(profile=profile, app_key=app_key, payload=payload or {})
28
+
29
+ @mcp.tool()
30
+ def app_get_form_schema(
31
+ profile: str = DEFAULT_PROFILE,
32
+ app_key: str = "",
33
+ form_type: int = 1,
34
+ being_draft: bool | None = None,
35
+ being_apply: bool | None = None,
36
+ audit_node_id: int | None = None,
37
+ ) -> JSONObject:
38
+ return self.app_get_form_schema(
39
+ profile=profile,
40
+ app_key=app_key,
41
+ form_type=form_type,
42
+ being_draft=being_draft,
43
+ being_apply=being_apply,
44
+ audit_node_id=audit_node_id,
45
+ )
46
+
47
+ @mcp.tool()
48
+ def app_get_edit_version_no(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
49
+ return self.app_get_edit_version_no(profile=profile, app_key=app_key)
50
+
51
+ @mcp.tool(description=self._high_risk_tool_description(operation="update", target="app form schema"))
52
+ def app_update_form_schema(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: JSONObject | None = None) -> JSONObject:
53
+ return self.app_update_form_schema(profile=profile, app_key=app_key, payload=payload or {})
54
+
55
+ @mcp.tool()
56
+ def app_edit_finished(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: JSONObject | None = None) -> JSONObject:
57
+ return self.app_edit_finished(profile=profile, app_key=app_key, payload=payload or {})
58
+
59
+ @mcp.tool()
60
+ def app_create(profile: str = DEFAULT_PROFILE, payload: JSONObject | None = None) -> JSONObject:
61
+ return self.app_create(profile=profile, payload=payload or {})
62
+
63
+ @mcp.tool(description=self._high_risk_tool_description(operation="delete", target="app configuration"))
64
+ def app_delete(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
65
+ return self.app_delete(profile=profile, app_key=app_key)
66
+
67
+ @mcp.tool()
68
+ def app_publish(
69
+ profile: str = DEFAULT_PROFILE,
70
+ app_key: str = "",
71
+ payload: JSONObject | None = None,
72
+ ) -> JSONObject:
73
+ return self.app_publish(profile=profile, app_key=app_key, payload=payload or {})
74
+
75
+ def app_list(self, *, profile: str, ship_auth: bool = False) -> JSONObject:
76
+ """Get all apps with full hierarchy from tag/apps endpoint."""
77
+ def runner(session_profile, context):
78
+ result = self.backend.request("GET", context, "/tag/apps")
79
+ return {
80
+ "profile": profile,
81
+ "ws_id": session_profile.selected_ws_id,
82
+ "items": result,
83
+ }
84
+
85
+ return self._run(profile, runner)
86
+
87
+ def app_search(self, *, profile: str, keyword: str = "", page_num: int = 1, page_size: int = 50) -> JSONObject:
88
+ """Search apps by keyword in name/title using backend search API.
89
+ Useful for finding BUG-related apps across all packages."""
90
+ def runner(session_profile, context):
91
+ # Use GET /app/item which supports queryKey search across all apps
92
+ params: JSONObject = {"pageNum": page_num, "pageSize": page_size}
93
+ if keyword:
94
+ params["queryKey"] = keyword
95
+
96
+ result = self.backend.request("GET", context, "/app/item", params=params)
97
+
98
+ # Extract app list from the response
99
+ apps = []
100
+ if isinstance(result, dict):
101
+ items = result.get("list", [])
102
+ for item in items:
103
+ if isinstance(item, dict):
104
+ apps.append({
105
+ "app_key": item.get("appKey"),
106
+ "title": item.get("title") or item.get("formTitle"),
107
+ "form_id": item.get("formId"),
108
+ "tag_id": item.get("tagId"),
109
+ "group_id": item.get("groupId"),
110
+ })
111
+
112
+ return {
113
+ "profile": profile,
114
+ "ws_id": session_profile.selected_ws_id,
115
+ "keyword": keyword,
116
+ "page_num": page_num,
117
+ "page_size": page_size,
118
+ "total": result.get("total") if isinstance(result, dict) else len(apps),
119
+ "apps": apps,
120
+ }
121
+
122
+ return self._run(profile, runner)
123
+
124
+ def app_get_base(self, *, profile: str, app_key: str) -> JSONObject:
125
+ self._require_app_key(app_key)
126
+
127
+ def runner(session_profile, context):
128
+ result = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
129
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
130
+
131
+ return self._run(profile, runner)
132
+
133
+ def app_update_base(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
134
+ self._require_app_key(app_key)
135
+ body = self._require_dict(payload)
136
+
137
+ def runner(session_profile, context):
138
+ result = self.backend.request("POST", context, f"/app/{app_key}/baseInfo", json_body=body)
139
+ return self._attach_human_review_notice(
140
+ {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result},
141
+ operation="update",
142
+ target="app base settings",
143
+ )
144
+
145
+ return self._run(profile, runner)
146
+
147
+ def app_get_form_schema(
148
+ self,
149
+ *,
150
+ profile: str,
151
+ app_key: str,
152
+ form_type: int,
153
+ being_draft: bool | None,
154
+ being_apply: bool | None,
155
+ audit_node_id: int | None,
156
+ ) -> JSONObject:
157
+ self._require_app_key(app_key)
158
+
159
+ def runner(session_profile, context):
160
+ params: JSONObject = {"type": form_type}
161
+ if being_draft is not None:
162
+ params["beingDraft"] = being_draft
163
+ if being_apply is not None:
164
+ params["beingApply"] = being_apply
165
+ if audit_node_id is not None:
166
+ params["auditNodeId"] = audit_node_id
167
+ result = self.backend.request("GET", context, f"/app/{app_key}/form", params=params)
168
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
169
+
170
+ return self._run(profile, runner)
171
+
172
+ def app_get_edit_version_no(self, *, profile: str, app_key: str) -> JSONObject:
173
+ self._require_app_key(app_key)
174
+
175
+ def runner(session_profile, context):
176
+ result = self.backend.request("GET", context, f"/app/{app_key}/editVersionNo")
177
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
178
+
179
+ return self._run(profile, runner)
180
+
181
+ def app_update_form_schema(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
182
+ self._require_app_key(app_key)
183
+ body = self._require_dict(payload)
184
+
185
+ def runner(session_profile, context):
186
+ result = self.backend.request("POST", context, f"/app/{app_key}/form", json_body=body)
187
+ return self._attach_human_review_notice(
188
+ {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result},
189
+ operation="update",
190
+ target="app form schema",
191
+ )
192
+
193
+ return self._run(profile, runner)
194
+
195
+ def app_edit_finished(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
196
+ self._require_app_key(app_key)
197
+ body = self._require_dict(payload)
198
+
199
+ def runner(session_profile, context):
200
+ result = self.backend.request("POST", context, f"/app/{app_key}/editFinished", json_body=body)
201
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
202
+
203
+ return self._run(profile, runner)
204
+
205
+ def app_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
206
+ body = self._require_dict(payload)
207
+
208
+ def runner(session_profile, context):
209
+ result = self.backend.request("POST", context, "/app", json_body=body)
210
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
211
+
212
+ return self._run(profile, runner)
213
+
214
+ def app_delete(self, *, profile: str, app_key: str) -> JSONObject:
215
+ self._require_app_key(app_key)
216
+
217
+ def runner(session_profile, context):
218
+ result = self.backend.request("DELETE", context, f"/app/{app_key}")
219
+ return self._attach_human_review_notice(
220
+ {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result},
221
+ operation="delete",
222
+ target="app configuration",
223
+ )
224
+
225
+ return self._run(profile, runner)
226
+
227
+ def app_publish(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
228
+ self._require_app_key(app_key)
229
+ body = self._require_dict(payload)
230
+
231
+ def runner(session_profile, context):
232
+ result = self.backend.request("POST", context, f"/app/{app_key}/publish", json_body=body)
233
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
234
+
235
+ return self._run(profile, runner)
236
+
237
+ def _require_app_key(self, app_key: str) -> None:
238
+ if not app_key:
239
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))