@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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +2 -2
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/formatters.py +176 -0
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.
|
|
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.
|
|
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
package/pyproject.toml
CHANGED
|
@@ -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="
|
|
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="
|
|
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="
|
|
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="
|
|
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,
|