@josephyan/qingflow-app-user-mcp 0.2.0-beta.93 → 0.2.0-beta.94

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@0.2.0-beta.93
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.94
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.93 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.94 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": "0.2.0-beta.93",
3
+ "version": "0.2.0-beta.94",
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 = "0.2.0b93"
7
+ version = "0.2.0b94"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  __all__ = ["__version__"]
7
7
 
8
- _FALLBACK_VERSION = "0.2.0b93"
8
+ _FALLBACK_VERSION = "0.2.0b94"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None:
@@ -12839,6 +12839,233 @@ _QUESTION_RELATION_SAVE_KEYS = (
12839
12839
  "sortConfig",
12840
12840
  )
12841
12841
 
12842
+ _RELATION_QUESTION_SAVE_KEYS = (
12843
+ "queId",
12844
+ "queTempId",
12845
+ "queType",
12846
+ "queOriginType",
12847
+ "queTitle",
12848
+ "queWidth",
12849
+ "scanType",
12850
+ "status",
12851
+ "required",
12852
+ "queHint",
12853
+ "linkedQuestions",
12854
+ "logicalShow",
12855
+ "queDefaultType",
12856
+ "queDefaultValue",
12857
+ "queDefaultValues",
12858
+ "subQueWidth",
12859
+ "innerQuestions",
12860
+ "minOpts",
12861
+ "maxOpts",
12862
+ "beingHide",
12863
+ "beingDesensitized",
12864
+ "relationDisplayMode",
12865
+ "customRenderConfig",
12866
+ )
12867
+
12868
+ _REFERENCE_CONFIG_SAVE_KEYS = (
12869
+ "referAppKey",
12870
+ "referQueId",
12871
+ "customButtonText",
12872
+ "beingTableSource",
12873
+ "referMatchRules",
12874
+ "canAddData",
12875
+ "dataAdditionButtonText",
12876
+ "canViewProcessLog",
12877
+ "optionalDataNum",
12878
+ "beingDataLogVisible",
12879
+ "beingDefaultFormulaAutoFillEnabled",
12880
+ "defaultValueMatchRules",
12881
+ "configShowForm",
12882
+ "configSortFieldId",
12883
+ "configAsc",
12884
+ "dataShowForm",
12885
+ "defaultRow",
12886
+ "fieldNameShow",
12887
+ "dataSortFieldId",
12888
+ "dataSortAsc",
12889
+ )
12890
+
12891
+ _REFERENCE_QUESTION_SAVE_KEYS = (
12892
+ "queId",
12893
+ "queTitle",
12894
+ "queType",
12895
+ "queAuth",
12896
+ "ordinal",
12897
+ )
12898
+
12899
+ _REFERENCE_FILL_RULE_SAVE_KEYS = (
12900
+ "queId",
12901
+ "relatedQueId",
12902
+ "queTitle",
12903
+ "relatedQueTitle",
12904
+ )
12905
+
12906
+ _REFERENCE_AUTH_QUESTION_SAVE_KEYS = (
12907
+ "queId",
12908
+ "queAuth",
12909
+ )
12910
+
12911
+
12912
+ def _copy_present_keys(
12913
+ source: dict[str, Any],
12914
+ keys: tuple[str, ...],
12915
+ *,
12916
+ keep_none_keys: tuple[str, ...] = (),
12917
+ ) -> dict[str, Any]:
12918
+ payload: dict[str, Any] = {}
12919
+ keep_none = set(keep_none_keys)
12920
+ for key in keys:
12921
+ if key not in source:
12922
+ continue
12923
+ value = source.get(key)
12924
+ if value is None and key not in keep_none:
12925
+ continue
12926
+ payload[key] = deepcopy(value)
12927
+ return payload
12928
+
12929
+
12930
+ def _normalize_reference_question_for_save(value: Any, *, ordinal: int) -> dict[str, Any] | None:
12931
+ if not isinstance(value, dict):
12932
+ return None
12933
+ payload = _copy_present_keys(value, _REFERENCE_QUESTION_SAVE_KEYS)
12934
+ que_id = _coerce_any_int(value.get("queId"))
12935
+ if que_id is not None:
12936
+ payload["queId"] = que_id
12937
+ if "ordinal" not in payload:
12938
+ payload["ordinal"] = _coerce_nonnegative_int(value.get("ordinal"))
12939
+ if payload.get("ordinal") is None:
12940
+ payload["ordinal"] = ordinal
12941
+ if not any(key in payload for key in ("queId", "queTitle", "queType")):
12942
+ return None
12943
+ return payload
12944
+
12945
+
12946
+ def _normalize_reference_fill_rule_for_save(value: Any) -> dict[str, Any] | None:
12947
+ if not isinstance(value, dict):
12948
+ return None
12949
+ payload = _copy_present_keys(value, _REFERENCE_FILL_RULE_SAVE_KEYS)
12950
+ que_id = _coerce_nonnegative_int(value.get("queId"))
12951
+ related_que_id = _coerce_nonnegative_int(value.get("relatedQueId", value.get("referQueId")))
12952
+ if que_id is not None:
12953
+ payload["queId"] = que_id
12954
+ if related_que_id is not None:
12955
+ payload["relatedQueId"] = related_que_id
12956
+ if "relatedQueTitle" not in payload and value.get("referQueTitle") is not None:
12957
+ payload["relatedQueTitle"] = str(value.get("referQueTitle") or "")
12958
+ if "queId" not in payload or "relatedQueId" not in payload:
12959
+ return None
12960
+ return payload
12961
+
12962
+
12963
+ def _normalize_reference_auth_question_for_save(value: Any) -> dict[str, Any] | None:
12964
+ if not isinstance(value, dict):
12965
+ return None
12966
+ payload = _copy_present_keys(value, _REFERENCE_AUTH_QUESTION_SAVE_KEYS)
12967
+ que_id = _coerce_any_int(value.get("queId"))
12968
+ if que_id is not None:
12969
+ payload["queId"] = que_id
12970
+ que_auth = _coerce_nonnegative_int(value.get("queAuth"))
12971
+ if que_auth is not None:
12972
+ payload["queAuth"] = que_auth
12973
+ sub_ques = [
12974
+ item
12975
+ for item in (
12976
+ _normalize_reference_auth_question_for_save(raw_item)
12977
+ for raw_item in cast(list[Any], value.get("subQues") or [])
12978
+ )
12979
+ if item is not None
12980
+ ]
12981
+ inner_ques = [
12982
+ item
12983
+ for item in (
12984
+ _normalize_reference_auth_question_for_save(raw_item)
12985
+ for raw_item in cast(list[Any], value.get("innerQues") or [])
12986
+ )
12987
+ if item is not None
12988
+ ]
12989
+ if sub_ques or "subQues" in value:
12990
+ payload["subQues"] = sub_ques
12991
+ if inner_ques or "innerQues" in value:
12992
+ payload["innerQues"] = inner_ques
12993
+ if "queId" not in payload or "queAuth" not in payload:
12994
+ return None
12995
+ return payload
12996
+
12997
+
12998
+ def _normalize_reference_config_for_save(
12999
+ reference: Any,
13000
+ *,
13001
+ field: dict[str, Any],
13002
+ ) -> dict[str, Any]:
13003
+ source = reference if isinstance(reference, dict) else {}
13004
+ payload = _copy_present_keys(source, _REFERENCE_CONFIG_SAVE_KEYS)
13005
+ if str(field.get("target_app_key") or "").strip():
13006
+ payload["referAppKey"] = str(field.get("target_app_key") or "").strip()
13007
+ if field.get("target_field_que_id") is not None:
13008
+ payload["referQueId"] = _coerce_nonnegative_int(field.get("target_field_que_id"))
13009
+ if field.get("field_name_show") is not None:
13010
+ payload["fieldNameShow"] = bool(field.get("field_name_show"))
13011
+
13012
+ refer_questions: list[dict[str, Any]] = []
13013
+ for index, raw_item in enumerate(cast(list[Any], source.get("referQuestions") or []), start=1):
13014
+ normalized_item = _normalize_reference_question_for_save(raw_item, ordinal=index)
13015
+ if normalized_item is not None:
13016
+ refer_questions.append(normalized_item)
13017
+ if refer_questions or "referQuestions" in source:
13018
+ payload["referQuestions"] = refer_questions
13019
+
13020
+ refer_fill_rules = [
13021
+ item
13022
+ for item in (
13023
+ _normalize_reference_fill_rule_for_save(raw_item)
13024
+ for raw_item in cast(list[Any], source.get("referFillRules") or [])
13025
+ )
13026
+ if item is not None
13027
+ ]
13028
+ if refer_fill_rules or "referFillRules" in source:
13029
+ payload["referFillRules"] = refer_fill_rules
13030
+
13031
+ refer_auth_ques = [
13032
+ item
13033
+ for item in (
13034
+ _normalize_reference_auth_question_for_save(raw_item)
13035
+ for raw_item in cast(list[Any], source.get("referAuthQues") or [])
13036
+ )
13037
+ if item is not None
13038
+ ]
13039
+ if refer_auth_ques or "referAuthQues" in source:
13040
+ payload["referAuthQues"] = refer_auth_ques
13041
+
13042
+ return payload
13043
+
13044
+
13045
+ def _normalize_relation_question_for_save(question: dict[str, Any], *, field: dict[str, Any]) -> dict[str, Any]:
13046
+ payload = _copy_present_keys(question, _RELATION_QUESTION_SAVE_KEYS)
13047
+ overlay_keys = _field_question_overlay_keys(field)
13048
+ que_id = _coerce_nonnegative_int(question.get("queId"))
13049
+ if que_id is not None:
13050
+ payload["queId"] = que_id
13051
+ que_temp_id = _coerce_nonnegative_int(question.get("queTempId"))
13052
+ if que_temp_id is not None and "queId" not in payload:
13053
+ payload["queTempId"] = que_temp_id
13054
+ payload["queType"] = _coerce_positive_int(question.get("queType")) or 25
13055
+ payload["queTitle"] = str(field.get("name") or question.get("queTitle") or "")
13056
+ if "required" in overlay_keys or "required" in question or field.get("required") is not None:
13057
+ payload["required"] = bool(field.get("required", question.get("required", False)))
13058
+ if "description" in overlay_keys:
13059
+ payload["queHint"] = "" if field.get("description") is None else str(field.get("description"))
13060
+ elif "queHint" in question and question.get("queHint") is not None:
13061
+ payload["queHint"] = str(question.get("queHint") or "")
13062
+ if field.get("default_type") is not None:
13063
+ payload["queDefaultType"] = _coerce_positive_int(field.get("default_type")) or 1
13064
+ if "default_value" in field:
13065
+ payload["queDefaultValue"] = field.get("default_value")
13066
+ payload["referenceConfig"] = _normalize_reference_config_for_save(question.get("referenceConfig"), field=field)
13067
+ return payload
13068
+
12842
13069
 
12843
13070
  def _build_form_save_base_payload(current_schema: dict[str, Any], title: str) -> dict[str, Any]:
12844
13071
  payload: dict[str, Any] = {"formTitle": title}
@@ -12899,7 +13126,7 @@ def _sync_question_title_references(value: Any, *, by_que_id: dict[int, str], by
12899
13126
  if not isinstance(value, dict):
12900
13127
  return
12901
13128
 
12902
- title_keys = ("queTitle", "supQueTitle", "_field_id")
13129
+ title_keys = ("queTitle", "_field_id")
12903
13130
  que_id = _coerce_nonnegative_int(value.get("queId"))
12904
13131
  replacement = None
12905
13132
  if que_id is not None and que_id in by_que_id:
@@ -12936,6 +13163,8 @@ def _materialize_preserved_question(field: dict[str, Any]) -> dict[str, Any] | N
12936
13163
  if "description" in overlay_keys:
12937
13164
  description = field.get("description")
12938
13165
  template["queHint"] = "" if description is None else str(description)
13166
+ if str(field.get("type") or "") == FieldType.relation.value:
13167
+ return _normalize_relation_question_for_save(template, field=field)
12939
13168
  return template
12940
13169
 
12941
13170
 
@@ -13043,6 +13272,7 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
13043
13272
  if field.get("target_field_que_id") is not None:
13044
13273
  reference["referQueId"] = field.get("target_field_que_id")
13045
13274
  question["referenceConfig"] = reference
13275
+ question = _normalize_relation_question_for_save(question, field=field)
13046
13276
  if field.get("type") == FieldType.department.value:
13047
13277
  scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
13048
13278
  question["deptSelectScopeType"] = scope_type