@qingflow-tech/qingflow-app-user-mcp 1.0.20 → 1.0.21

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-user-mcp@1.0.20
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.21
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.20 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.21 qingflow-app-user-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-user-mcp",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory 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.21"
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:
@@ -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 []
@@ -1153,6 +1267,10 @@ _FORMATTERS = {
1153
1267
  "workspace_select": _format_workspace_select,
1154
1268
  "app_list": _format_app_items,
1155
1269
  "app_get": _format_app_get,
1270
+ "portal_list": _format_portal_list,
1271
+ "portal_get": _format_portal_get,
1272
+ "view_get": _format_view_get,
1273
+ "chart_get": _format_chart_get,
1156
1274
  "record_list": _format_record_list,
1157
1275
  "record_access": _format_record_access,
1158
1276
  "record_get": _format_record_get,