@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.
Files changed (70) hide show
  1. package/README.md +5 -3
  2. package/docs/local-agent-install.md +21 -5
  3. package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
  4. package/npm/lib/runtime.mjs +168 -12
  5. package/package.json +1 -1
  6. package/pyproject.toml +4 -1
  7. package/skills/qingflow-app-builder/SKILL.md +155 -22
  8. package/skills/qingflow-app-builder/references/create-app.md +51 -21
  9. package/skills/qingflow-app-builder/references/environments.md +1 -1
  10. package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
  11. package/skills/qingflow-app-builder/references/gotchas.md +28 -1
  12. package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
  13. package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
  14. package/skills/qingflow-app-builder/references/update-flow.md +112 -25
  15. package/skills/qingflow-app-builder/references/update-layout.md +11 -24
  16. package/skills/qingflow-app-builder/references/update-schema.md +1 -23
  17. package/skills/qingflow-app-builder/references/update-views.md +87 -21
  18. package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
  19. package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
  20. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
  21. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
  22. package/src/qingflow_mcp/__init__.py +1 -1
  23. package/src/qingflow_mcp/backend_client.py +210 -0
  24. package/src/qingflow_mcp/builder_facade/models.py +1252 -3
  25. package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
  26. package/src/qingflow_mcp/cli/__init__.py +1 -0
  27. package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
  28. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  29. package/src/qingflow_mcp/cli/commands/auth.py +78 -0
  30. package/src/qingflow_mcp/cli/commands/builder.py +515 -0
  31. package/src/qingflow_mcp/cli/commands/common.py +62 -0
  32. package/src/qingflow_mcp/cli/commands/imports.py +96 -0
  33. package/src/qingflow_mcp/cli/commands/record.py +304 -0
  34. package/src/qingflow_mcp/cli/commands/task.py +89 -0
  35. package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
  36. package/src/qingflow_mcp/cli/context.py +48 -0
  37. package/src/qingflow_mcp/cli/formatters.py +355 -0
  38. package/src/qingflow_mcp/cli/json_io.py +50 -0
  39. package/src/qingflow_mcp/cli/main.py +149 -0
  40. package/src/qingflow_mcp/config.py +39 -0
  41. package/src/qingflow_mcp/import_store.py +121 -0
  42. package/src/qingflow_mcp/list_type_labels.py +24 -0
  43. package/src/qingflow_mcp/response_trim.py +668 -0
  44. package/src/qingflow_mcp/server.py +160 -18
  45. package/src/qingflow_mcp/server_app_builder.py +275 -68
  46. package/src/qingflow_mcp/server_app_user.py +219 -191
  47. package/src/qingflow_mcp/session_store.py +41 -1
  48. package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
  49. package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
  50. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
  51. package/src/qingflow_mcp/solution/executor.py +107 -11
  52. package/src/qingflow_mcp/solution/spec_models.py +2 -0
  53. package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
  54. package/src/qingflow_mcp/tools/app_tools.py +419 -12
  55. package/src/qingflow_mcp/tools/approval_tools.py +571 -72
  56. package/src/qingflow_mcp/tools/auth_tools.py +398 -2
  57. package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
  58. package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
  59. package/src/qingflow_mcp/tools/directory_tools.py +203 -31
  60. package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
  61. package/src/qingflow_mcp/tools/file_tools.py +1 -0
  62. package/src/qingflow_mcp/tools/import_tools.py +2150 -0
  63. package/src/qingflow_mcp/tools/package_tools.py +18 -4
  64. package/src/qingflow_mcp/tools/portal_tools.py +31 -0
  65. package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
  66. package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
  67. package/src/qingflow_mcp/tools/solution_tools.py +115 -3
  68. package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
  69. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  70. package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from ..backend_client import BackendClient
8
+ from ..config import get_feedback_app_key, get_feedback_base_url, get_feedback_qsource_token, normalize_base_url
9
+ from ..errors import QingflowApiError, raise_tool_error
10
+ from ..json_types import JSONObject
11
+
12
+
13
+ CATEGORY_MAP = {
14
+ "feature_request": "功能需求",
15
+ "bug_report": "问题反馈",
16
+ "ux_feedback": "体验建议",
17
+ "unsupported_scenario": "不支持场景",
18
+ "other": "其他",
19
+ }
20
+
21
+ IMPACT_SCOPE_MAP = {
22
+ "personal": "仅个人",
23
+ "small_team": "小范围团队",
24
+ "cross_team": "跨团队",
25
+ "global": "全局",
26
+ }
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class FeedbackTools:
31
+ backend: BackendClient
32
+ mcp_side: str
33
+
34
+ def register(self, mcp: FastMCP) -> None:
35
+ @mcp.tool()
36
+ def feedback_submit(
37
+ category: str = "",
38
+ title: str = "",
39
+ description: str = "",
40
+ expected_behavior: str | None = None,
41
+ actual_behavior: str | None = None,
42
+ impact_scope: str | None = None,
43
+ tool_name: str | None = None,
44
+ app_key: str | None = None,
45
+ record_id: str | int | None = None,
46
+ workflow_node_id: str | int | None = None,
47
+ note: str | None = None,
48
+ ) -> JSONObject:
49
+ """Submit product feedback to the Qingflow MCP team.
50
+
51
+ Use this when the current MCP capability is unsupported, awkward, or still cannot satisfy the user's need
52
+ after reasonable attempts. This helper writes through the internal q-source feedback intake, does not
53
+ require Qingflow login or workspace selection, and should be called only after explicit user confirmation.
54
+ """
55
+ return self.feedback_submit(
56
+ category=category,
57
+ title=title,
58
+ description=description,
59
+ expected_behavior=expected_behavior,
60
+ actual_behavior=actual_behavior,
61
+ impact_scope=impact_scope,
62
+ tool_name=tool_name,
63
+ app_key=app_key,
64
+ record_id=record_id,
65
+ workflow_node_id=workflow_node_id,
66
+ note=note,
67
+ )
68
+
69
+ def feedback_submit(
70
+ self,
71
+ *,
72
+ category: str,
73
+ title: str,
74
+ description: str,
75
+ expected_behavior: str | None,
76
+ actual_behavior: str | None,
77
+ impact_scope: str | None,
78
+ tool_name: str | None,
79
+ app_key: str | None,
80
+ record_id: str | int | None,
81
+ workflow_node_id: str | int | None,
82
+ note: str | None,
83
+ ) -> JSONObject:
84
+ qsource_token = get_feedback_qsource_token()
85
+ if not qsource_token:
86
+ raise_tool_error(
87
+ QingflowApiError(
88
+ category="config",
89
+ message=(
90
+ "feedback_submit is not configured. Set "
91
+ "feedback.qsource_token or QINGFLOW_MCP_FEEDBACK_QSOURCE_TOKEN first."
92
+ ),
93
+ details={"error_code": "FEEDBACK_NOT_CONFIGURED"},
94
+ )
95
+ )
96
+
97
+ base_url = get_feedback_base_url()
98
+ if not base_url:
99
+ raise_tool_error(
100
+ QingflowApiError.config_error(
101
+ "feedback_submit requires a base_url. Configure feedback.base_url or default_base_url."
102
+ )
103
+ )
104
+
105
+ normalized_payload = self._build_payload(
106
+ category=category,
107
+ title=title,
108
+ description=description,
109
+ expected_behavior=expected_behavior,
110
+ actual_behavior=actual_behavior,
111
+ impact_scope=impact_scope,
112
+ tool_name=tool_name,
113
+ app_key=app_key,
114
+ record_id=record_id,
115
+ workflow_node_id=workflow_node_id,
116
+ note=note,
117
+ )
118
+
119
+ try:
120
+ response = self.backend.public_request_with_meta(
121
+ "POST",
122
+ base_url,
123
+ f"/qsource/{qsource_token}",
124
+ json_body=normalized_payload,
125
+ unwrap=True,
126
+ qf_version=None,
127
+ )
128
+ except QingflowApiError as exc:
129
+ raise_tool_error(exc)
130
+ result = response.data if isinstance(response.data, dict) else {}
131
+ feedback_request_id = result.get("requestId") if isinstance(result, dict) else None
132
+
133
+ return {
134
+ "ok": True,
135
+ "request_route": {
136
+ "base_url": normalize_base_url(base_url) or base_url,
137
+ "qf_version": None,
138
+ "qf_version_source": "not_applicable",
139
+ },
140
+ "submission_mode": "qsource_passive",
141
+ "feedback_target": {
142
+ "app_key": get_feedback_app_key(),
143
+ "mcp_side": self.mcp_side,
144
+ },
145
+ "normalized_payload": normalized_payload,
146
+ "feedback_request_id": feedback_request_id,
147
+ }
148
+
149
+ def _build_payload(
150
+ self,
151
+ *,
152
+ category: str,
153
+ title: str,
154
+ description: str,
155
+ expected_behavior: str | None,
156
+ actual_behavior: str | None,
157
+ impact_scope: str | None,
158
+ tool_name: str | None,
159
+ app_key: str | None,
160
+ record_id: str | int | None,
161
+ workflow_node_id: str | int | None,
162
+ note: str | None,
163
+ ) -> JSONObject:
164
+ payload: JSONObject = {
165
+ "title": self._require_text("title", title),
166
+ "category": self._normalize_label("category", category, CATEGORY_MAP, required=True),
167
+ "description": self._require_text("description", description),
168
+ "submit_method": "AI代提",
169
+ "status": "待处理",
170
+ "mcp_side": self.mcp_side,
171
+ }
172
+
173
+ optional_text = {
174
+ "expected_result": expected_behavior,
175
+ "actual_behavior": actual_behavior,
176
+ "tool_name": tool_name,
177
+ "app_key": app_key,
178
+ "note": note,
179
+ }
180
+ for key, value in optional_text.items():
181
+ normalized = self._normalize_optional_text(value)
182
+ if normalized is not None:
183
+ payload[key] = normalized
184
+
185
+ if impact_scope is not None and str(impact_scope).strip():
186
+ payload["impact_scope"] = self._normalize_label("impact_scope", impact_scope, IMPACT_SCOPE_MAP, required=False)
187
+
188
+ if record_id is not None and str(record_id).strip():
189
+ payload["record_id"] = str(record_id).strip()
190
+ if workflow_node_id is not None and str(workflow_node_id).strip():
191
+ payload["workflow_node_id"] = str(workflow_node_id).strip()
192
+
193
+ return payload
194
+
195
+ def _normalize_label(self, field: str, value: str, mapping: dict[str, str], *, required: bool) -> str:
196
+ text = str(value or "").strip()
197
+ if not text:
198
+ if required:
199
+ raise_tool_error(QingflowApiError.config_error(f"{field} is required"))
200
+ return ""
201
+
202
+ canonical = text.lower()
203
+ if canonical in mapping:
204
+ return mapping[canonical]
205
+ if text in mapping.values():
206
+ return text
207
+ supported_values = list(mapping.keys()) + list(mapping.values())
208
+ raise_tool_error(
209
+ QingflowApiError(
210
+ category="config",
211
+ message=f"{field} must be one of the supported canonical values or labels",
212
+ details={
213
+ "error_code": "FEEDBACK_INVALID_INPUT",
214
+ "field": field,
215
+ "supported_values": supported_values,
216
+ },
217
+ )
218
+ )
219
+
220
+ def _require_text(self, field: str, value: str) -> str:
221
+ normalized = str(value or "").strip()
222
+ if not normalized:
223
+ raise_tool_error(QingflowApiError.config_error(f"{field} is required"))
224
+ return normalized
225
+
226
+ def _normalize_optional_text(self, value: str | None) -> str | None:
227
+ if value is None:
228
+ return None
229
+ normalized = str(value).strip()
230
+ return normalized or None
@@ -178,6 +178,7 @@ class FileTools(ToolBase):
178
178
  "download_url": download_url,
179
179
  "attachment_value": {
180
180
  "value": download_url,
181
+ "otherInfo": file_name,
181
182
  "name": file_name,
182
183
  },
183
184
  "comment_file_info": {