@josephyan/qingflow-app-builder-mcp 0.2.0-beta.73 → 0.2.0-beta.74
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +1 -1
- package/src/qingflow_mcp/backend_client.py +102 -0
- package/src/qingflow_mcp/builder_facade/service.py +609 -5
- package/src/qingflow_mcp/cli/commands/builder.py +33 -1
- package/src/qingflow_mcp/cli/commands/repo.py +80 -0
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/config.py +147 -0
- package/src/qingflow_mcp/repository_store.py +71 -0
- package/src/qingflow_mcp/response_trim.py +4 -0
- package/src/qingflow_mcp/server_app_builder.py +26 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +110 -0
- package/src/qingflow_mcp/tools/repository_dev_tools.py +533 -0
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.74
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.74 qingflow-app-builder-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -61,7 +61,7 @@ Note:
|
|
|
61
61
|
- `portal_apply` uses replace semantics for sections; remove a section by omitting it from the next full sections list. `publish=false` only guarantees draft/base-info updates, and `chart_ref/view_ref` resolve by `id/key` first and exact unique name second.
|
|
62
62
|
- `app_get_charts` is the compact discovery path for current chart inventory; use it before `app_charts_apply` when you need exact `chart_id` values.
|
|
63
63
|
- `chart_get` returns one chart's base info and config only; public builder flows should treat data reads as user-side access, not builder config access.
|
|
64
|
-
- `portal_list` is the discovery path for
|
|
64
|
+
- `portal_list` is the discovery path for builder-configurable portals.
|
|
65
65
|
- `portal_get` returns portal-level config detail plus a component inventory; it does not inline chart/view detail or user-side chart/view data.
|
|
66
66
|
- `view_get` returns one view's definition detail only; use `record_list` separately when you need rows from that view.
|
|
67
67
|
- `app_schema_apply` / `app_layout_apply` / `app_flow_apply` / `app_views_apply` now perform planning, normalization, and dependency checks internally; when prechecks block, read the returned blocking issues and `suggested_next_call` directly from the apply result.
|
|
@@ -88,6 +88,30 @@ class BackendClient:
|
|
|
88
88
|
unwrap=unwrap,
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
+
def public_request_with_headers(
|
|
92
|
+
self,
|
|
93
|
+
method: str,
|
|
94
|
+
base_url: str,
|
|
95
|
+
path: str,
|
|
96
|
+
*,
|
|
97
|
+
params: JSONObject | None = None,
|
|
98
|
+
json_body: JSONValue = None,
|
|
99
|
+
unwrap: bool = True,
|
|
100
|
+
qf_version: str | None = None,
|
|
101
|
+
headers: dict[str, str] | None = None,
|
|
102
|
+
) -> BackendResponse:
|
|
103
|
+
request_headers = self._base_headers(None, None, qf_version=qf_version)
|
|
104
|
+
if headers:
|
|
105
|
+
request_headers.update({key: value for key, value in headers.items() if value is not None})
|
|
106
|
+
return self._request_with_meta(
|
|
107
|
+
method,
|
|
108
|
+
self._build_url(base_url, path),
|
|
109
|
+
params=params,
|
|
110
|
+
json_body=json_body,
|
|
111
|
+
headers=request_headers,
|
|
112
|
+
unwrap=unwrap,
|
|
113
|
+
)
|
|
114
|
+
|
|
91
115
|
def request(
|
|
92
116
|
self,
|
|
93
117
|
method: str,
|
|
@@ -131,6 +155,54 @@ class BackendClient:
|
|
|
131
155
|
unwrap=unwrap,
|
|
132
156
|
)
|
|
133
157
|
|
|
158
|
+
def stream_request(
|
|
159
|
+
self,
|
|
160
|
+
method: str,
|
|
161
|
+
context: BackendRequestContext,
|
|
162
|
+
path: str,
|
|
163
|
+
*,
|
|
164
|
+
params: JSONObject | None = None,
|
|
165
|
+
json_body: JSONValue = None,
|
|
166
|
+
headers: dict[str, str] | None = None,
|
|
167
|
+
) -> list[str]:
|
|
168
|
+
request_headers = self._base_headers(
|
|
169
|
+
context.token,
|
|
170
|
+
context.ws_id,
|
|
171
|
+
context.qf_request_id,
|
|
172
|
+
qf_version=context.qf_version,
|
|
173
|
+
)
|
|
174
|
+
if headers:
|
|
175
|
+
request_headers.update({key: value for key, value in headers.items() if value is not None})
|
|
176
|
+
return self._stream_lines(
|
|
177
|
+
method,
|
|
178
|
+
self._build_url(context.base_url, path),
|
|
179
|
+
params=params,
|
|
180
|
+
json_body=json_body,
|
|
181
|
+
headers=request_headers,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def public_stream_request(
|
|
185
|
+
self,
|
|
186
|
+
method: str,
|
|
187
|
+
base_url: str,
|
|
188
|
+
path: str,
|
|
189
|
+
*,
|
|
190
|
+
params: JSONObject | None = None,
|
|
191
|
+
json_body: JSONValue = None,
|
|
192
|
+
headers: dict[str, str] | None = None,
|
|
193
|
+
qf_version: str | None = None,
|
|
194
|
+
) -> list[str]:
|
|
195
|
+
request_headers = self._base_headers(None, None, qf_version=qf_version)
|
|
196
|
+
if headers:
|
|
197
|
+
request_headers.update({key: value for key, value in headers.items() if value is not None})
|
|
198
|
+
return self._stream_lines(
|
|
199
|
+
method,
|
|
200
|
+
self._build_url(base_url, path),
|
|
201
|
+
params=params,
|
|
202
|
+
json_body=json_body,
|
|
203
|
+
headers=request_headers,
|
|
204
|
+
)
|
|
205
|
+
|
|
134
206
|
def describe_route(self, context: BackendRequestContext) -> JSONObject:
|
|
135
207
|
qf_version, source = self._resolve_qf_version(context.qf_version)
|
|
136
208
|
if context.qf_version is not None and context.qf_version_source:
|
|
@@ -430,6 +502,36 @@ class BackendClient:
|
|
|
430
502
|
assert last_error is not None
|
|
431
503
|
raise last_error
|
|
432
504
|
|
|
505
|
+
def _stream_lines(
|
|
506
|
+
self,
|
|
507
|
+
method: str,
|
|
508
|
+
url: str,
|
|
509
|
+
*,
|
|
510
|
+
params: JSONObject | None,
|
|
511
|
+
json_body: JSONValue,
|
|
512
|
+
headers: dict[str, str],
|
|
513
|
+
) -> list[str]:
|
|
514
|
+
try:
|
|
515
|
+
with self._client.stream(method.upper(), url, params=params, json=json_body, headers=headers) as response:
|
|
516
|
+
request_id = headers["Qf-Request-Id"]
|
|
517
|
+
if response.status_code >= 400:
|
|
518
|
+
raw_bytes = response.read()
|
|
519
|
+
payload: JSONValue
|
|
520
|
+
try:
|
|
521
|
+
payload = response.json()
|
|
522
|
+
except ValueError:
|
|
523
|
+
payload = raw_bytes.decode("utf-8", errors="replace")
|
|
524
|
+
raise QingflowApiError(
|
|
525
|
+
category="http",
|
|
526
|
+
message=self._extract_message(payload) or f"HTTP {response.status_code}",
|
|
527
|
+
backend_code=self._extract_code(payload),
|
|
528
|
+
request_id=request_id,
|
|
529
|
+
http_status=response.status_code,
|
|
530
|
+
)
|
|
531
|
+
return [line for line in response.iter_lines()]
|
|
532
|
+
except httpx.RequestError as exc:
|
|
533
|
+
raise QingflowApiError(category="network", message=str(exc), request_id=headers["Qf-Request-Id"])
|
|
534
|
+
|
|
433
535
|
def _parse_response(self, response: httpx.Response, request_id: str, *, unwrap: bool) -> JSONValue:
|
|
434
536
|
payload: JSONValue
|
|
435
537
|
try:
|