@qingflow-tech/qingflow-app-user-mcp 1.0.36 → 1.0.38

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.36
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.38
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.36 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.38 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.36",
3
+ "version": "1.0.38",
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.36"
7
+ version = "1.0.38"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -9,7 +9,7 @@ import random
9
9
  import re
10
10
  import string
11
11
  import tempfile
12
- from typing import Any, cast
12
+ from typing import Any, NoReturn, cast
13
13
  from urllib.parse import quote_plus, unquote_plus
14
14
  from uuid import uuid4
15
15
 
@@ -11378,11 +11378,14 @@ class AiBuilderFacade:
11378
11378
 
11379
11379
  if failed_items:
11380
11380
  successful_changes = bool(created_ids or updated_ids or removed_ids or reordered)
11381
+ primary_failure = failed_items[0]
11382
+ primary_error_code = str(primary_failure.get("error_code") or "").strip()
11383
+ primary_message = str(primary_failure.get("message") or "").strip()
11381
11384
  return finalize({
11382
11385
  "status": "partial_success" if successful_changes else "failed",
11383
- "error_code": "CHART_APPLY_PARTIAL" if successful_changes else "CHART_APPLY_FAILED",
11386
+ "error_code": "CHART_APPLY_PARTIAL" if successful_changes else primary_error_code or "CHART_APPLY_FAILED",
11384
11387
  "recoverable": True,
11385
- "message": "applied some chart operations; at least one chart operation failed" if successful_changes else "one or more chart operations failed",
11388
+ "message": "applied some chart operations; at least one chart operation failed" if successful_changes else primary_message or "one or more chart operations failed",
11386
11389
  "normalized_args": normalized_args,
11387
11390
  "missing_fields": [],
11388
11391
  "allowed_values": {"chart.chart_type": [member.value for member in PublicChartType], "chart.filter.operator": [member.value for member in ViewFilterOperator]},
@@ -14615,7 +14618,18 @@ def _build_qingbi_chart_field_lookup(
14615
14618
  field_lookup: dict[str, dict[str, Any]],
14616
14619
  ) -> dict[str, Any]:
14617
14620
  by_selector: dict[str, list[dict[str, Any]]] = {}
14618
- form_by_que_id = field_lookup.get("by_que_id") or {}
14621
+ form_by_que_id = dict(field_lookup.get("by_que_id") or {})
14622
+ if not form_by_que_id:
14623
+ for bucket_name in ("by_name", "by_field_id"):
14624
+ bucket = field_lookup.get(bucket_name) or {}
14625
+ if not isinstance(bucket, dict):
14626
+ continue
14627
+ for form_field in bucket.values():
14628
+ if not isinstance(form_field, dict):
14629
+ continue
14630
+ que_id = _coerce_any_int(form_field.get("que_id"))
14631
+ if que_id is not None:
14632
+ form_by_que_id.setdefault(que_id, form_field)
14619
14633
 
14620
14634
  def add_selector(key: Any, field: dict[str, Any]) -> None:
14621
14635
  normalized = str(key or "").strip()
@@ -15095,13 +15109,109 @@ def _build_public_chart_filter_matrix(
15095
15109
  "fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
15096
15110
  "fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
15097
15111
  "judgeType": judge_map.get(operator, "equal"),
15098
- "judgeValues": values,
15112
+ "judgeValue": _build_qingbi_chart_filter_judge_value(
15113
+ operator=operator,
15114
+ values=values,
15115
+ form_field=form_field,
15116
+ chart_type=chart_type,
15117
+ ),
15118
+ "judgeValueDetailList": [],
15099
15119
  "matchType": 1,
15100
15120
  }
15101
15121
  )
15102
15122
  return [group] if group else []
15103
15123
 
15104
15124
 
15125
+ def _build_qingbi_chart_filter_judge_value(
15126
+ *,
15127
+ operator: str,
15128
+ values: list[Any],
15129
+ form_field: dict[str, Any],
15130
+ chart_type: str,
15131
+ ) -> str | None:
15132
+ if operator in {ViewFilterOperator.is_empty.value, ViewFilterOperator.not_empty.value}:
15133
+ return None
15134
+ if operator == ViewFilterOperator.in_.value:
15135
+ return "<&&>".join(
15136
+ _qingbi_chart_filter_value_to_text(value=value, form_field=form_field, chart_type=chart_type) for value in values
15137
+ )
15138
+ if not values:
15139
+ return ""
15140
+ return _qingbi_chart_filter_value_to_text(value=values[0], form_field=form_field, chart_type=chart_type)
15141
+
15142
+
15143
+ def _qingbi_chart_filter_value_to_text(*, value: Any, form_field: dict[str, Any], chart_type: str) -> str:
15144
+ option_details = [
15145
+ item
15146
+ for item in (form_field.get("option_details") or [])
15147
+ if isinstance(item, dict) and item.get("id") is not None and item.get("value") is not None
15148
+ ]
15149
+ if not option_details:
15150
+ if isinstance(value, dict):
15151
+ for key in ("value", "label", "name", "title"):
15152
+ raw = str(value.get(key) or "").strip()
15153
+ if raw:
15154
+ return raw
15155
+ if value.get("id") is not None:
15156
+ return str(value.get("id"))
15157
+ return _stringify_condition_value(value)
15158
+
15159
+ option_by_value = {str(item.get("value") or "").strip(): item for item in option_details if str(item.get("value") or "").strip()}
15160
+ option_by_id = {str(item.get("id")): item for item in option_details if item.get("id") is not None}
15161
+
15162
+ def resolve(raw: Any) -> str | None:
15163
+ text = _stringify_condition_value(raw).strip()
15164
+ if not text:
15165
+ return None
15166
+ matched = option_by_value.get(text) or option_by_id.get(text)
15167
+ if not matched:
15168
+ return None
15169
+ return str(matched.get("value") or "").strip()
15170
+
15171
+ def reject(raw: Any) -> NoReturn:
15172
+ allowed = [
15173
+ {"id": str(item.get("id")), "value": str(item.get("value"))}
15174
+ for item in option_details
15175
+ ]
15176
+ _raise_chart_rule(
15177
+ rule_code="CHART_FILTER_OPTION_VALUE_UNSUPPORTED",
15178
+ chart_type=chart_type,
15179
+ message="chart filter value is not in the field option list",
15180
+ expected="Use an existing option label or option id from the target field schema.",
15181
+ actual={
15182
+ "field_name": form_field.get("name"),
15183
+ "field_id": form_field.get("que_id"),
15184
+ "value": raw,
15185
+ "allowed_values": allowed,
15186
+ },
15187
+ offending_fields=[{"field_name": form_field.get("name"), "field_id": form_field.get("que_id"), "options": allowed}],
15188
+ next_action="Read schema first, then pass one of the allowed option labels or option ids.",
15189
+ )
15190
+
15191
+ if isinstance(value, dict):
15192
+ for key in ("value", "label", "name", "title"):
15193
+ if value.get(key) is not None:
15194
+ resolved = resolve(value.get(key))
15195
+ if resolved is not None:
15196
+ return resolved
15197
+ reject(value.get(key))
15198
+ if value.get("id") is not None:
15199
+ resolved = resolve(value.get("id"))
15200
+ if resolved is not None:
15201
+ return resolved
15202
+ reject(value.get("id"))
15203
+ reject(value)
15204
+ if isinstance(value, int):
15205
+ resolved = resolve(value)
15206
+ if resolved is not None:
15207
+ return resolved
15208
+ reject(value)
15209
+ resolved = resolve(value)
15210
+ if resolved is not None:
15211
+ return resolved
15212
+ reject(value)
15213
+
15214
+
15105
15215
  def _build_public_chart_config_payload(
15106
15216
  *,
15107
15217
  patch: ChartUpsertPatch,