@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
|
@@ -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
|