@qingflow-tech/qingflow-app-builder-mcp 1.0.20 → 1.0.22

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 @qingflow-tech/qingflow-app-builder-mcp@1.0.20
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.22
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.20 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.22 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
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 = "1.0.20"
7
+ version = "1.0.22"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -11,7 +11,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
11
11
 
12
12
  get_parser = chart_subparsers.add_parser("get", help="读取报表数据")
13
13
  get_parser.add_argument("--chart-id", required=True)
14
- get_parser.set_defaults(handler=_handle_get, format_hint="generic")
14
+ get_parser.set_defaults(handler=_handle_get, format_hint="chart_get")
15
15
 
16
16
 
17
17
  def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
@@ -10,11 +10,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
10
10
  portal_subparsers = parser.add_subparsers(dest="portal_command", required=True)
11
11
 
12
12
  list_parser = portal_subparsers.add_parser("list", help="列出当前用户可访问的门户")
13
- list_parser.set_defaults(handler=_handle_list, format_hint="generic")
13
+ list_parser.set_defaults(handler=_handle_list, format_hint="portal_list")
14
14
 
15
15
  get_parser = portal_subparsers.add_parser("get", help="读取门户内容清单")
16
16
  get_parser.add_argument("--dash-key", required=True)
17
- get_parser.set_defaults(handler=_handle_get, format_hint="generic")
17
+ get_parser.set_defaults(handler=_handle_get, format_hint="portal_get")
18
18
 
19
19
 
20
20
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
@@ -65,7 +65,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
65
65
  member_candidates.add_argument("--record-id")
66
66
  member_candidates.add_argument("--workflow-node-id", type=int)
67
67
  member_candidates.add_argument("--fields-file")
68
- member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="")
68
+ member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="record_candidates")
69
69
 
70
70
  department_candidates = record_subparsers.add_parser("department-candidates", help="读取部门字段候选项")
71
71
  department_candidates.add_argument("--app-key", required=True)
@@ -76,7 +76,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
76
76
  department_candidates.add_argument("--record-id")
77
77
  department_candidates.add_argument("--workflow-node-id", type=int)
78
78
  department_candidates.add_argument("--fields-file")
79
- department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="")
79
+ department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="record_candidates")
80
80
 
81
81
  list_parser = record_subparsers.add_parser("list", help="列出记录")
82
82
  list_parser.add_argument("--app-key", required=True)
@@ -11,7 +11,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
11
11
 
12
12
  get_parser = view_subparsers.add_parser("get", help="读取视图资源描述")
13
13
  get_parser.add_argument("--view-id", required=True)
14
- get_parser.set_defaults(handler=_handle_get, format_hint="generic")
14
+ get_parser.set_defaults(handler=_handle_get, format_hint="view_get")
15
15
 
16
16
 
17
17
  def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
@@ -227,6 +227,120 @@ def _format_app_get(result: dict[str, Any]) -> str:
227
227
  _append_warnings(lines, result.get("warnings"))
228
228
  return "\n".join(lines) + "\n"
229
229
 
230
+
231
+ def _format_portal_list(result: dict[str, Any]) -> str:
232
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
233
+ items = data.get("items") if isinstance(data.get("items"), list) else []
234
+ rows: list[list[str]] = []
235
+ for item in items:
236
+ if not isinstance(item, dict):
237
+ continue
238
+ rows.append(
239
+ [
240
+ str(item.get("dash_key") or ""),
241
+ str(item.get("dash_name") or ""),
242
+ ",".join(str(tag_id) for tag_id in item.get("package_tag_ids") or []),
243
+ ]
244
+ )
245
+ text = _render_titled_table("Portals", ["dash_key", "name", "package_tag_ids"], rows).rstrip("\n")
246
+ lines = [text, f"Total: {data.get('total') if data.get('total') is not None else len(items)}"]
247
+ _append_warnings(lines, result.get("warnings"))
248
+ _append_verification(lines, result.get("verification"))
249
+ return "\n".join(lines) + "\n"
250
+
251
+
252
+ def _format_portal_get(result: dict[str, Any]) -> str:
253
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
254
+ components = data.get("components") if isinstance(data.get("components"), list) else []
255
+ lines = [
256
+ f"Portal: {data.get('dash_name') or '-'}",
257
+ f"Dash Key: {data.get('dash_key') or '-'}",
258
+ f"Components: {data.get('component_count') if data.get('component_count') is not None else len(components)}",
259
+ ]
260
+ package_tag_ids = data.get("package_tag_ids") if isinstance(data.get("package_tag_ids"), list) else []
261
+ if package_tag_ids:
262
+ lines.append("Package Tag IDs: " + ", ".join(str(item) for item in package_tag_ids))
263
+ if components:
264
+ lines.append("Component Refs:")
265
+ for item in components[:12]:
266
+ if not isinstance(item, dict):
267
+ continue
268
+ prefix = f"- {item.get('source_type') or 'unknown'}"
269
+ title = item.get("title")
270
+ chart_ref = item.get("chart_ref") if isinstance(item.get("chart_ref"), dict) else {}
271
+ view_ref = item.get("view_ref") if isinstance(item.get("view_ref"), dict) else {}
272
+ if chart_ref:
273
+ lines.append(f"{prefix}: {title or chart_ref.get('chart_name') or '-'} / chart_id={chart_ref.get('chart_id') or '-'}")
274
+ elif view_ref:
275
+ lines.append(
276
+ f"{prefix}: {title or view_ref.get('view_name') or '-'} / "
277
+ f"view_id={view_ref.get('view_id') or '-'} / app_key={view_ref.get('app_key') or '-'}"
278
+ )
279
+ else:
280
+ lines.append(f"{prefix}: {title or '-'}")
281
+ if len(components) > 12:
282
+ lines.append(f"... {len(components) - 12} more")
283
+ _append_warnings(lines, result.get("warnings"))
284
+ _append_verification(lines, result.get("verification"))
285
+ return "\n".join(lines) + "\n"
286
+
287
+
288
+ def _format_view_get(result: dict[str, Any]) -> str:
289
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
290
+ export_capability = data.get("export_capability") if isinstance(data.get("export_capability"), dict) else {}
291
+ visible_columns = data.get("visible_columns") if isinstance(data.get("visible_columns"), list) else []
292
+ lines = [
293
+ f"View: {data.get('view_name') or '-'}",
294
+ f"View ID: {data.get('view_id') or '-'}",
295
+ f"View Key: {data.get('view_key') or '-'}",
296
+ f"App Key: {data.get('app_key') or '-'}",
297
+ f"View Type: {data.get('view_type') or '-'}",
298
+ f"Analysis Supported: {data.get('analysis_supported')}",
299
+ ]
300
+ if export_capability:
301
+ lines.append(
302
+ "Export: "
303
+ f"supported={export_capability.get('supported')} / "
304
+ f"tool={export_capability.get('tool') or '-'} / "
305
+ f"requires_app_key={export_capability.get('requires_app_key')}"
306
+ )
307
+ lines.append(f"Visible Columns: {len(visible_columns)}")
308
+ for column in visible_columns[:20]:
309
+ lines.append(f"- {column}")
310
+ if len(visible_columns) > 20:
311
+ lines.append(f"... {len(visible_columns) - 20} more")
312
+ _append_warnings(lines, result.get("warnings"))
313
+ _append_verification(lines, result.get("verification"))
314
+ return "\n".join(lines) + "\n"
315
+
316
+
317
+ def _format_chart_get(result: dict[str, Any]) -> str:
318
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
319
+ chart_data = data.get("data") if isinstance(data.get("data"), dict) else {}
320
+ config = data.get("config") if isinstance(data.get("config"), dict) else {}
321
+ lines = [
322
+ f"Chart: {data.get('chart_name') or '-'}",
323
+ f"Chart ID: {data.get('chart_id') or '-'}",
324
+ f"Chart Type: {data.get('chart_type') or '-'}",
325
+ f"Data Source: {data.get('data_source_type') or '-'} / {data.get('data_source_id') or '-'}",
326
+ f"Data Loaded: {bool(chart_data)}",
327
+ f"Config Loaded: {bool(config)}",
328
+ ]
329
+ summary = chart_data.get("summary") if isinstance(chart_data.get("summary"), dict) else {}
330
+ rows = chart_data.get("rows") if isinstance(chart_data.get("rows"), list) else []
331
+ if summary:
332
+ lines.append("Summary:")
333
+ lines.extend(f"- {line}" for line in _dict_scalar_lines(summary))
334
+ if rows:
335
+ lines.append(f"Rows: {len(rows)}")
336
+ lines.append(json.dumps(rows[0], ensure_ascii=False))
337
+ elif config:
338
+ lines.append("Config Keys: " + ", ".join(str(key) for key in list(config.keys())[:12]))
339
+ _append_warnings(lines, result.get("warnings"))
340
+ _append_verification(lines, result.get("verification"))
341
+ return "\n".join(lines) + "\n"
342
+
343
+
230
344
  def _format_record_list(result: dict[str, Any]) -> str:
231
345
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
232
346
  items = data.get("items") if isinstance(data.get("items"), list) else []
@@ -281,6 +395,63 @@ def _format_record_access(result: dict[str, Any]) -> str:
281
395
  return "\n".join(lines) + "\n"
282
396
 
283
397
 
398
+ def _format_record_candidates(result: dict[str, Any]) -> str:
399
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
400
+ selection = data.get("selection") if isinstance(data.get("selection"), dict) else {}
401
+ pagination = data.get("pagination") if isinstance(data.get("pagination"), dict) else {}
402
+ items = data.get("items") if isinstance(data.get("items"), list) else []
403
+ lines = [
404
+ f"Field: {selection.get('field_title') or '-'} ({selection.get('field_id') or '-'})",
405
+ f"App Key: {selection.get('app_key') or '-'}",
406
+ f"Scope Source: {data.get('scope_source') or '-'}",
407
+ f"Keyword: {selection.get('keyword') if selection.get('keyword') not in (None, '') else '-'}",
408
+ ]
409
+ if selection.get("record_id") not in (None, "") or selection.get("workflow_node_id") not in (None, "") or selection.get("fields_present"):
410
+ lines.append(
411
+ "Runtime Context: "
412
+ f"record_id={selection.get('record_id') or '-'} / "
413
+ f"workflow_node_id={selection.get('workflow_node_id') or '-'} / "
414
+ f"fields_present={selection.get('fields_present')}"
415
+ )
416
+ if pagination:
417
+ lines.append(
418
+ "Pagination: "
419
+ f"page={pagination.get('page')} / "
420
+ f"page_size={pagination.get('page_size')} / "
421
+ f"returned={pagination.get('returned_items')} / "
422
+ f"total={pagination.get('reported_total')} / "
423
+ f"pages={pagination.get('page_amount')}"
424
+ )
425
+ lines.append(f"Candidates: {len(items)}")
426
+ for item in items[:20]:
427
+ if not isinstance(item, dict):
428
+ lines.append(f"- {item}")
429
+ continue
430
+ parts = [str(item.get("value") or item.get("name") or item.get("id") or "-")]
431
+ if item.get("id") not in (None, ""):
432
+ parts.append(f"id={item.get('id')}")
433
+ if item.get("userId") not in (None, ""):
434
+ parts.append(f"userId={item.get('userId')}")
435
+ if item.get("email") not in (None, ""):
436
+ parts.append(f"email={item.get('email')}")
437
+ if item.get("path") not in (None, ""):
438
+ parts.append(f"path={item.get('path')}")
439
+ sources = item.get("sources") if isinstance(item.get("sources"), list) else []
440
+ source_labels = [
441
+ str(source.get("kind"))
442
+ for source in sources
443
+ if isinstance(source, dict) and source.get("kind") not in (None, "")
444
+ ]
445
+ if source_labels:
446
+ parts.append("sources=" + ",".join(source_labels))
447
+ lines.append("- " + " / ".join(parts))
448
+ if len(items) > 20:
449
+ lines.append(f"... {len(items) - 20} more")
450
+ _append_warnings(lines, result.get("warnings"))
451
+ _append_verification(lines, result.get("verification"))
452
+ return "\n".join(lines) + "\n"
453
+
454
+
284
455
  def _format_record_get(result: dict[str, Any]) -> str:
285
456
  record = result.get("record") if isinstance(result.get("record"), dict) else {}
286
457
  app = result.get("app") if isinstance(result.get("app"), dict) else {}
@@ -1153,8 +1324,13 @@ _FORMATTERS = {
1153
1324
  "workspace_select": _format_workspace_select,
1154
1325
  "app_list": _format_app_items,
1155
1326
  "app_get": _format_app_get,
1327
+ "portal_list": _format_portal_list,
1328
+ "portal_get": _format_portal_get,
1329
+ "view_get": _format_view_get,
1330
+ "chart_get": _format_chart_get,
1156
1331
  "record_list": _format_record_list,
1157
1332
  "record_access": _format_record_access,
1333
+ "record_candidates": _format_record_candidates,
1158
1334
  "record_get": _format_record_get,
1159
1335
  "record_logs": _format_record_logs,
1160
1336
  "record_write": _format_record_write,