@josephyan/qingflow-app-user-mcp 1.1.27 → 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.27
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.27 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.27",
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.27"
7
+ version = "1.1.29"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -157,6 +157,7 @@ MATCH_TYPE_ACCURACY = 1
157
157
  MATCH_TYPE_QUESTION = 2
158
158
  JUDGE_EQUAL = 0
159
159
  JUDGE_UNEQUAL = 1
160
+ JUDGE_INCLUDE = 2
160
161
  JUDGE_GREATER_OR_EQUAL = 5
161
162
  JUDGE_LESS_OR_EQUAL = 7
162
163
  JUDGE_EQUAL_ANY = 9
@@ -6855,6 +6856,11 @@ class AiBuilderFacade:
6855
6856
  if (field_id := _coerce_nonnegative_int(entry.get("field_id"))) is not None
6856
6857
  }
6857
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 []
6858
6864
  return {
6859
6865
  "status": "success",
6860
6866
  "error_code": None,
@@ -6870,6 +6876,9 @@ class AiBuilderFacade:
6870
6876
  "warnings": warnings,
6871
6877
  "verification": verification,
6872
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"),
6873
6882
  "query_conditions": query_conditions,
6874
6883
  "buttons_config": buttons_config,
6875
6884
  "associated_resources_config": associated_resources_config,
@@ -12580,7 +12589,8 @@ def _match_mapping_operator_to_judge_type(operator: Any, *, reason_path: str) ->
12580
12589
  "le": JUDGE_LESS_OR_EQUAL,
12581
12590
  "<=": JUDGE_LESS_OR_EQUAL,
12582
12591
  "less_or_equal": JUDGE_LESS_OR_EQUAL,
12583
- "contains": JUDGE_FUZZY_MATCH,
12592
+ "contains": JUDGE_INCLUDE,
12593
+ "include": JUDGE_INCLUDE,
12584
12594
  "like": JUDGE_FUZZY_MATCH,
12585
12595
  "fuzzy": JUDGE_FUZZY_MATCH,
12586
12596
  "fuzzy_match": JUDGE_FUZZY_MATCH,
@@ -21152,6 +21162,20 @@ def _merge_view_summary_with_config(
21152
21162
  question_entries_by_id=query_question_entries_by_id,
21153
21163
  )
21154
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
21155
21179
  if any(key in config for key in ("asosChartVisible", "asosChartConfig", "asosChartIdList", "limitType")):
21156
21180
  summary["associated_resources_config"] = _extract_view_associated_resources_config(config)
21157
21181
  config_enriched = True
@@ -22331,7 +22355,7 @@ def _translate_flow_condition_rule(*, field: dict[str, Any], rule: dict[str, Any
22331
22355
  base["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
22332
22356
  base["judgeValues"] = [_stringify_condition_value(value) for value in values]
22333
22357
  elif operator == "contains":
22334
- base["judgeType"] = JUDGE_FUZZY_MATCH
22358
+ base["judgeType"] = JUDGE_INCLUDE
22335
22359
  base["judgeValues"] = [_stringify_condition_value(values[0])]
22336
22360
  elif operator == "gte":
22337
22361
  base["judgeType"] = JUDGE_GREATER_OR_EQUAL
@@ -24098,7 +24122,7 @@ def _translate_view_filter_rule(*, field: dict[str, Any], rule: dict[str, Any])
24098
24122
  payload["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
24099
24123
  payload["judgeValues"] = judge_values
24100
24124
  elif operator == "contains":
24101
- payload["judgeType"] = JUDGE_FUZZY_MATCH
24125
+ payload["judgeType"] = JUDGE_INCLUDE
24102
24126
  payload["judgeValues"] = judge_values[:1] if judge_values else []
24103
24127
  elif operator == "gte":
24104
24128
  payload["judgeType"] = JUDGE_GREATER_OR_EQUAL
@@ -24122,7 +24146,8 @@ def _resolve_view_filter_values(
24122
24146
  ) -> tuple[list[str], list[dict[str, Any]], dict[str, Any] | None]:
24123
24147
  field_type = str(field.get("type") or FieldType.text.value)
24124
24148
  if field_type not in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
24125
- 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
24126
24151
 
24127
24152
  option_details = [
24128
24153
  item
@@ -24202,6 +24227,23 @@ def _build_view_filter_value_details(values: list[Any]) -> list[dict[str, Any]]:
24202
24227
  return details
24203
24228
 
24204
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
+
24205
24247
  def _coerce_view_filter_value_detail(value: dict[str, Any]) -> dict[str, Any]:
24206
24248
  item_id = value.get("id")
24207
24249
  item_value = value.get("value")
@@ -24229,9 +24271,16 @@ def _normalize_view_filter_groups_for_compare(groups: Any) -> list[list[dict[str
24229
24271
  continue
24230
24272
  normalized_details = []
24231
24273
  for detail in raw_rule.get("judgeValueDetails") or []:
24232
- 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:
24233
24279
  continue
24234
- 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)
24235
24284
  normalized_rules.append(
24236
24285
  {
24237
24286
  "queId": _coerce_positive_int(raw_rule.get("queId")) or 0,
@@ -24283,6 +24332,78 @@ def _view_filter_groups_equivalent(expected: Any, actual: Any) -> bool:
24283
24332
  return _view_filter_groups_signature(expected) == _view_filter_groups_signature(actual)
24284
24333
 
24285
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
+
24286
24407
  def _infer_status_field_id(fields: list[dict[str, Any]]) -> str | None:
24287
24408
  preferred_names = {"status", "状态", "订单状态", "审批状态", "流程状态"}
24288
24409
  for field in fields: