@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 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.73
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.73 qingflow-app-builder-mcp
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "0.2.0-beta.73",
3
+ "version": "0.2.0-beta.74",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b73"
7
+ version = "0.2.0b74"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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 accessible portals.
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: