@josephyan/qingflow-app-user-mcp 1.1.28 → 1.1.29

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-user-mcp@1.1.28
6
+ npm install @josephyan/qingflow-app-user-mcp@1.1.29
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@1.1.28 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@1.1.29 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
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.1.28"
7
+ version = "1.1.29"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -6856,6 +6856,11 @@ class AiBuilderFacade:
6856
6856
  if (field_id := _coerce_nonnegative_int(entry.get("field_id"))) is not None
6857
6857
  }
6858
6858
  query_conditions = _extract_view_query_conditions_config(config, question_entries_by_id=question_entries_by_id)
6859
+ filter_groups = _public_view_filter_groups_from_match_rules(
6860
+ config.get("viewgraphLimit") if isinstance(config, dict) else [],
6861
+ question_entries_by_id=question_entries_by_id,
6862
+ )
6863
+ filters = filter_groups[0] if len(filter_groups) == 1 else []
6859
6864
  return {
6860
6865
  "status": "success",
6861
6866
  "error_code": None,
@@ -6871,6 +6876,9 @@ class AiBuilderFacade:
6871
6876
  "warnings": warnings,
6872
6877
  "verification": verification,
6873
6878
  "verified": all(bool(value) for value in verification.values()),
6879
+ "filters": filters,
6880
+ "filter_groups": filter_groups,
6881
+ "filter_logic": "or" if len(filter_groups) > 1 else ("and" if filter_groups else "none"),
6874
6882
  "query_conditions": query_conditions,
6875
6883
  "buttons_config": buttons_config,
6876
6884
  "associated_resources_config": associated_resources_config,
@@ -21154,6 +21162,20 @@ def _merge_view_summary_with_config(
21154
21162
  question_entries_by_id=query_question_entries_by_id,
21155
21163
  )
21156
21164
  config_enriched = True
21165
+ if "viewgraphLimit" in config:
21166
+ filter_question_entries_by_id = dict(question_entries_by_id)
21167
+ for entry in canonical_question_entries:
21168
+ field_id = _coerce_nonnegative_int(entry.get("field_id"))
21169
+ if field_id is not None:
21170
+ filter_question_entries_by_id[field_id] = entry
21171
+ filter_groups = _public_view_filter_groups_from_match_rules(
21172
+ config.get("viewgraphLimit"),
21173
+ question_entries_by_id=filter_question_entries_by_id,
21174
+ )
21175
+ summary["filter_groups"] = filter_groups
21176
+ summary["filters"] = filter_groups[0] if len(filter_groups) == 1 else []
21177
+ summary["filter_logic"] = "or" if len(filter_groups) > 1 else ("and" if filter_groups else "none")
21178
+ config_enriched = True
21157
21179
  if any(key in config for key in ("asosChartVisible", "asosChartConfig", "asosChartIdList", "limitType")):
21158
21180
  summary["associated_resources_config"] = _extract_view_associated_resources_config(config)
21159
21181
  config_enriched = True
@@ -24124,7 +24146,8 @@ def _resolve_view_filter_values(
24124
24146
  ) -> tuple[list[str], list[dict[str, Any]], dict[str, Any] | None]:
24125
24147
  field_type = str(field.get("type") or FieldType.text.value)
24126
24148
  if field_type not in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
24127
- return [_stringify_condition_value(value) for value in values], _build_view_filter_value_details(values), None
24149
+ string_values = [_stringify_condition_value(value) for value in values]
24150
+ return string_values, _build_view_filter_scalar_value_details(values), None
24128
24151
 
24129
24152
  option_details = [
24130
24153
  item
@@ -24204,6 +24227,23 @@ def _build_view_filter_value_details(values: list[Any]) -> list[dict[str, Any]]:
24204
24227
  return details
24205
24228
 
24206
24229
 
24230
+ def _build_view_filter_scalar_value_details(values: list[Any]) -> list[dict[str, Any]]:
24231
+ details: list[dict[str, Any]] = []
24232
+ for value in values:
24233
+ if isinstance(value, dict):
24234
+ raw_value = value.get("value")
24235
+ if raw_value is None:
24236
+ raw_value = value.get("dataValue")
24237
+ if raw_value is None:
24238
+ raw_value = value.get("id")
24239
+ if raw_value is None:
24240
+ continue
24241
+ details.append(_coerce_view_filter_value_detail({"value": _stringify_condition_value(raw_value)}))
24242
+ continue
24243
+ details.append(_coerce_view_filter_value_detail({"value": _stringify_condition_value(value)}))
24244
+ return details
24245
+
24246
+
24207
24247
  def _coerce_view_filter_value_detail(value: dict[str, Any]) -> dict[str, Any]:
24208
24248
  item_id = value.get("id")
24209
24249
  item_value = value.get("value")
@@ -24231,9 +24271,16 @@ def _normalize_view_filter_groups_for_compare(groups: Any) -> list[list[dict[str
24231
24271
  continue
24232
24272
  normalized_details = []
24233
24273
  for detail in raw_rule.get("judgeValueDetails") or []:
24234
- if not isinstance(detail, dict) or detail.get("id") is None:
24274
+ if not isinstance(detail, dict):
24275
+ continue
24276
+ item_id = detail.get("id")
24277
+ item_value = detail.get("value")
24278
+ if item_id is None and item_value is None:
24235
24279
  continue
24236
- normalized_details.append({"id": detail.get("id"), "value": str(detail.get("value") or detail.get("id"))})
24280
+ normalized_detail = {"value": str(item_value if item_value is not None else item_id)}
24281
+ if item_id is not None:
24282
+ normalized_detail["id"] = item_id
24283
+ normalized_details.append(normalized_detail)
24237
24284
  normalized_rules.append(
24238
24285
  {
24239
24286
  "queId": _coerce_positive_int(raw_rule.get("queId")) or 0,
@@ -24285,6 +24332,78 @@ def _view_filter_groups_equivalent(expected: Any, actual: Any) -> bool:
24285
24332
  return _view_filter_groups_signature(expected) == _view_filter_groups_signature(actual)
24286
24333
 
24287
24334
 
24335
+ def _public_view_filter_groups_from_match_rules(
24336
+ groups: Any,
24337
+ *,
24338
+ question_entries_by_id: dict[int, dict[str, Any]] | None = None,
24339
+ ) -> list[list[dict[str, Any]]]:
24340
+ question_entries_by_id = question_entries_by_id or {}
24341
+ public_groups: list[list[dict[str, Any]]] = []
24342
+ for raw_group in groups or []:
24343
+ if not isinstance(raw_group, list):
24344
+ continue
24345
+ public_group: list[dict[str, Any]] = []
24346
+ for raw_rule in raw_group:
24347
+ if not isinstance(raw_rule, dict):
24348
+ continue
24349
+ que_id = _coerce_positive_int(raw_rule.get("queId")) or 0
24350
+ field_name = str((question_entries_by_id.get(que_id) or {}).get("name") or raw_rule.get("queTitle") or "").strip()
24351
+ if not field_name:
24352
+ continue
24353
+ values = _public_view_filter_values_from_rule(raw_rule)
24354
+ operator = _public_view_filter_operator_from_judge_type(raw_rule.get("judgeType"), values=values)
24355
+ item: dict[str, Any] = {
24356
+ "field_name": field_name,
24357
+ "operator": operator,
24358
+ }
24359
+ if operator not in {"is_empty", "not_empty"}:
24360
+ if operator == "in":
24361
+ item["values"] = values
24362
+ elif len(values) == 1:
24363
+ item["value"] = values[0]
24364
+ elif values:
24365
+ item["values"] = values
24366
+ public_group.append(item)
24367
+ if public_group:
24368
+ public_groups.append(public_group)
24369
+ return public_groups
24370
+
24371
+
24372
+ def _public_view_filter_values_from_rule(rule: dict[str, Any]) -> list[str]:
24373
+ detail_values: list[str] = []
24374
+ for detail in rule.get("judgeValueDetails") or []:
24375
+ if not isinstance(detail, dict):
24376
+ continue
24377
+ value = detail.get("value")
24378
+ if value is None:
24379
+ value = detail.get("id")
24380
+ if value is None:
24381
+ continue
24382
+ text = str(value).strip()
24383
+ if text:
24384
+ detail_values.append(text)
24385
+ if detail_values:
24386
+ return detail_values
24387
+ return [str(value) for value in (rule.get("judgeValues") or []) if str(value).strip()]
24388
+
24389
+
24390
+ def _public_view_filter_operator_from_judge_type(judge_type: Any, *, values: list[str]) -> str:
24391
+ numeric = _coerce_positive_int(judge_type)
24392
+ if numeric == JUDGE_EQUAL:
24393
+ return "eq" if values else "is_empty"
24394
+ if numeric == JUDGE_UNEQUAL:
24395
+ return "neq" if values else "not_empty"
24396
+ if numeric in {JUDGE_INCLUDE, JUDGE_FUZZY_MATCH}:
24397
+ return "contains"
24398
+ if numeric == JUDGE_GREATER_OR_EQUAL:
24399
+ return "gte"
24400
+ if numeric == JUDGE_LESS_OR_EQUAL:
24401
+ return "lte"
24402
+ if numeric in {JUDGE_EQUAL_ANY, JUDGE_INCLUDE_ANY}:
24403
+ return "in"
24404
+ return f"raw:{numeric}" if numeric is not None else "raw"
24405
+
24406
+
24288
24407
  def _infer_status_field_id(fields: list[dict[str, Any]]) -> str | None:
24289
24408
  preferred_names = {"status", "状态", "订单状态", "审批状态", "流程状态"}
24290
24409
  for field in fields: