@josephyan/qingflow-app-builder-mcp 0.2.0-beta.23 → 0.2.0-beta.24

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-builder-mcp@0.2.0-beta.23
6
+ npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.24
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.23 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.24 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "0.2.0-beta.23",
3
+ "version": "0.2.0-beta.24",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution 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 = "0.2.0b23"
7
+ version = "0.2.0b24"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -38,6 +38,7 @@ ATTACHMENT_QUE_TYPES = {13}
38
38
  RELATION_QUE_TYPES = {25}
39
39
  SUBTABLE_QUE_TYPES = {18}
40
40
  VERIFY_UNSUPPORTED_WRITE_QUE_TYPES = {14, 34, 35, 36}
41
+ LAYOUT_ONLY_QUE_TYPES = {24}
41
42
  DEPARTMENT_MEMBER_JUDGE_PREFIX = "deptId_"
42
43
  JUDGE_EQUAL = 0
43
44
  JUDGE_UNEQUAL = 1
@@ -1131,10 +1132,12 @@ class RecordTools(ToolBase):
1131
1132
  self._ensure_allowed_analyze_keys(
1132
1133
  item,
1133
1134
  location=f"metrics[{idx}]",
1134
- allowed_keys={"op", "field_id", "fieldId", "alias"},
1135
+ allowed_keys={"op", "type", "agg", "aggregation", "field_id", "fieldId", "alias"},
1135
1136
  example="{'op': 'sum', 'field_id': 7, 'alias': '总金额'}",
1136
1137
  )
1137
- op = _normalize_optional_text(item.get("op"))
1138
+ op = _normalize_optional_text(
1139
+ item.get("op") or item.get("type") or item.get("agg") or item.get("aggregation")
1140
+ )
1138
1141
  if op not in supported_ops:
1139
1142
  raise RecordInputError(
1140
1143
  message=f"metrics[{idx}] uses unsupported op '{op}'",
@@ -1153,12 +1156,8 @@ class RecordTools(ToolBase):
1153
1156
  details={"location": f"metrics[{idx}]", "field": _field_ref_payload(field), "op": op},
1154
1157
  )
1155
1158
  elif item.get("field_id", item.get("fieldId")) is not None:
1156
- raise RecordInputError(
1157
- message=f"metrics[{idx}] with op 'count' must not include field_id",
1158
- error_code="INVALID_ANALYZE_METRIC",
1159
- fix_hint="Remove field_id from count metrics.",
1160
- details={"location": f"metrics[{idx}]", "op": op},
1161
- )
1159
+ # LLM 经常给 count 传 field_id,静默忽略而非报错
1160
+ pass
1162
1161
  alias = _normalize_optional_text(item.get("alias"))
1163
1162
  if alias is None:
1164
1163
  if op == "count":
@@ -3995,16 +3994,19 @@ def _build_field_index(schema: JSONObject) -> FieldIndex:
3995
3994
  *[(question, False) for question in _flatten_questions(schema.get("formQues"))],
3996
3995
  ]
3997
3996
  for question, is_base_question in all_questions:
3997
+ if not _should_index_question(question):
3998
+ continue
3998
3999
  que_id = _coerce_count(question.get("queId"))
3999
4000
  title = _stringify_json(question.get("queTitle")).strip()
4000
4001
  if que_id is None or que_id < 0 or not title:
4001
4002
  continue
4003
+ can_edit = question.get("canEdit")
4002
4004
  field = FormField(
4003
4005
  que_id=que_id,
4004
4006
  que_title=title,
4005
4007
  que_type=_coerce_count(question.get("queType")),
4006
4008
  required=bool(question.get("required") or question.get("beingRequired")),
4007
- readonly=bool(question.get("readonly") or question.get("beingReadonly") or is_base_question),
4009
+ readonly=bool(question.get("readonly") or question.get("beingReadonly") or is_base_question or can_edit is False),
4008
4010
  system=bool(question.get("system") or question.get("beingSystem") or is_base_question),
4009
4011
  options=_extract_question_options(question),
4010
4012
  aliases=[],
@@ -4023,16 +4025,35 @@ def _build_field_index(schema: JSONObject) -> FieldIndex:
4023
4025
  def _flatten_questions(payload: JSONValue) -> list[JSONObject]:
4024
4026
  flattened: list[JSONObject] = []
4025
4027
  if isinstance(payload, dict):
4026
- if "queId" in payload or "queTitle" in payload:
4028
+ is_question = "queId" in payload or "queTitle" in payload
4029
+ if is_question:
4027
4030
  flattened.append(payload)
4028
- for value in payload.values():
4029
- flattened.extend(_flatten_questions(value))
4031
+ for key in ("subQuestions", "innerQuestions", "subQues"):
4032
+ value = payload.get(key)
4033
+ if isinstance(value, list):
4034
+ flattened.extend(_flatten_questions(value))
4035
+ if not is_question:
4036
+ for key in ("baseQues", "formQues"):
4037
+ value = payload.get(key)
4038
+ if isinstance(value, list):
4039
+ flattened.extend(_flatten_questions(value))
4030
4040
  elif isinstance(payload, list):
4031
4041
  for item in payload:
4032
4042
  flattened.extend(_flatten_questions(item))
4033
4043
  return flattened
4034
4044
 
4035
4045
 
4046
+ def _should_index_question(question: JSONObject) -> bool:
4047
+ if bool(question.get("beingHide") or question.get("hidden")):
4048
+ return False
4049
+ if _coerce_count(question.get("quoteId")) is not None:
4050
+ return False
4051
+ que_type = _coerce_count(question.get("queType"))
4052
+ if que_type in LAYOUT_ONLY_QUE_TYPES:
4053
+ return False
4054
+ return True
4055
+
4056
+
4036
4057
  def _extract_question_options(question: JSONObject) -> list[str]:
4037
4058
  options = question.get("options")
4038
4059
  if not isinstance(options, list):