@qingflow-tech/qingflow-app-user-mcp 1.0.9 → 1.0.11

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.9
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.11
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.9 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.11 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.9",
3
+ "version": "1.0.11",
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.9"
7
+ version = "1.0.11"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -35,6 +35,8 @@ metadata:
35
35
  13. Do not look for any extra context bucket in update schema; lookup behavior stays inline on the field definitions themselves
36
36
  14. When update context feels unstable, trust `record_update_schema_get`'s route-aware matched-view result over guessed `view_id` or remembered UI scope
37
37
  15. If single-record detail/readback matters, prefer `record_get` after the write and read top-level `fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs; use `record_list(..., output_profile="normalized")` only for batch row-shaped normalized readback
38
+ 16. For batch updates, read top-level `mode`, `dry_run`, `total`, `succeeded`, `failed`, `needs_confirmation`, `updated_record_ids`, `write_executed`, `safe_to_retry`, `verification_status`, and `items[].row_number/status/record_id`
39
+ 17. If `write_executed=true`, do not blindly retry the whole batch; use `items[]` and `updated_record_ids` to decide whether only failed rows need repair
38
40
 
39
41
  ## Do Not
40
42
 
@@ -1893,8 +1893,9 @@ class PortalComponentPositionPatch(StrictModel):
1893
1893
  pc_h: int = Field(default=8, validation_alias=AliasChoices("pc_h", "pcH", "h"))
1894
1894
  mobile_x: int = Field(default=0, validation_alias=AliasChoices("mobile_x", "mobileX"))
1895
1895
  mobile_y: int = Field(default=0, validation_alias=AliasChoices("mobile_y", "mobileY"))
1896
- mobile_w: int = Field(default=12, validation_alias=AliasChoices("mobile_w", "mobileW"))
1896
+ mobile_w: int = Field(default=6, validation_alias=AliasChoices("mobile_w", "mobileW"))
1897
1897
  mobile_h: int = Field(default=8, validation_alias=AliasChoices("mobile_h", "mobileH"))
1898
+ mobile_provided: bool = Field(default=False, exclude=True)
1898
1899
 
1899
1900
  @model_validator(mode="before")
1900
1901
  @classmethod
@@ -1904,6 +1905,8 @@ class PortalComponentPositionPatch(StrictModel):
1904
1905
  payload = dict(value)
1905
1906
  pc = payload.pop("pc", None)
1906
1907
  mobile = payload.pop("mobile", None)
1908
+ mobile_keys = {"mobile_x", "mobileX", "mobile_y", "mobileY", "mobile_w", "mobileW", "mobile_h", "mobileH"}
1909
+ mobile_provided = isinstance(mobile, dict) or any(key in payload for key in mobile_keys)
1907
1910
  if isinstance(pc, dict):
1908
1911
  if "pc_x" not in payload and "x" in pc:
1909
1912
  payload["pc_x"] = pc.get("x")
@@ -1922,6 +1925,7 @@ class PortalComponentPositionPatch(StrictModel):
1922
1925
  payload["mobile_w"] = mobile.get("cols")
1923
1926
  if "mobile_h" not in payload and "rows" in mobile:
1924
1927
  payload["mobile_h"] = mobile.get("rows")
1928
+ payload["mobile_provided"] = mobile_provided
1925
1929
  return payload
1926
1930
 
1927
1931
 
@@ -2003,9 +2007,10 @@ class PortalSectionPatch(StrictModel):
2003
2007
  class PortalApplyRequest(StrictModel):
2004
2008
  dash_key: str | None = None
2005
2009
  dash_name: str | None = None
2006
- package_tag_id: int | None = None
2010
+ package_tag_id: int | None = Field(default=None, validation_alias=AliasChoices("package_tag_id", "packageTagId", "package_id", "packageId"))
2007
2011
  publish: bool = True
2008
2012
  sections: list[PortalSectionPatch] = Field(default_factory=list)
2013
+ layout_preset: str | None = Field(default=None, validation_alias=AliasChoices("layout_preset", "layoutPreset"))
2009
2014
  visibility: VisibilityPatch | None = None
2010
2015
  auth: dict[str, Any] | None = None
2011
2016
  icon: str | None = None
@@ -2014,6 +2019,33 @@ class PortalApplyRequest(StrictModel):
2014
2019
  dash_global_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_global_config", "dashGlobalConfig"))
2015
2020
  config: dict[str, Any] = Field(default_factory=dict)
2016
2021
 
2022
+ @model_validator(mode="before")
2023
+ @classmethod
2024
+ def normalize_compat_payload(cls, value: Any) -> Any:
2025
+ if not isinstance(value, dict):
2026
+ return value
2027
+ payload = dict(value)
2028
+ if "dash_name" not in payload and "dashName" not in payload and "name" in payload:
2029
+ payload["dash_name"] = payload.pop("name")
2030
+ if "sections" not in payload and "pages" in payload:
2031
+ pages = payload.pop("pages")
2032
+ if not isinstance(pages, list):
2033
+ raise ValueError("portal pages must be a list")
2034
+ if len(pages) != 1:
2035
+ raise ValueError("portal_apply currently supports a single page; pass one page or flatten components into sections")
2036
+ page = pages[0]
2037
+ if not isinstance(page, dict):
2038
+ raise ValueError("portal pages[0] must be an object")
2039
+ components = page.get("components")
2040
+ if not isinstance(components, list) or not components:
2041
+ raise ValueError("portal pages[0].components must be a non-empty list")
2042
+ payload["sections"] = components
2043
+ if "theme" in payload:
2044
+ payload.pop("theme")
2045
+ if "type" in payload:
2046
+ payload.pop("type")
2047
+ return payload
2048
+
2017
2049
  @model_validator(mode="after")
2018
2050
  def validate_shape(self) -> "PortalApplyRequest":
2019
2051
  if not self.dash_key and not self.package_tag_id:
@@ -2024,6 +2056,8 @@ class PortalApplyRequest(StrictModel):
2024
2056
  raise ValueError("portal apply requires a non-empty sections list when creating a portal")
2025
2057
  if self.visibility is not None and self.auth is not None:
2026
2058
  raise ValueError("visibility and auth cannot be provided together")
2059
+ if self.layout_preset is not None and self.layout_preset not in {"auto", "dashboard_2col", "dashboard_3col"}:
2060
+ raise ValueError("layout_preset must be one of: auto, dashboard_2col, dashboard_3col")
2027
2061
  return self
2028
2062
 
2029
2063
 
@@ -2034,8 +2068,11 @@ FieldUpdatePatch.model_rebuild()
2034
2068
 
2035
2069
  class AppGetResponse(StrictModel):
2036
2070
  app_key: str
2071
+ app_name: str | None = None
2072
+ name: str | None = None
2037
2073
  title: str | None = None
2038
2074
  app_icon: str | None = None
2075
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2039
2076
  visibility: dict[str, Any] = Field(default_factory=dict)
2040
2077
  tag_ids: list[int] = Field(default_factory=list)
2041
2078
  publish_status: int | None = None
@@ -2060,6 +2097,8 @@ class AppGetFieldsResponse(StrictModel):
2060
2097
  app_key: str
2061
2098
  fields: list[dict[str, Any]] = Field(default_factory=list)
2062
2099
  field_count: int = 0
2100
+ chart_fields: list[dict[str, Any]] = Field(default_factory=list)
2101
+ chart_field_count: int = 0
2063
2102
  form_settings: dict[str, Any] = Field(default_factory=dict)
2064
2103
 
2065
2104
 
@@ -2107,6 +2146,7 @@ class PortalReadSummaryResponse(StrictModel):
2107
2146
  dash_name: str | None = None
2108
2147
  package_tag_ids: list[int] = Field(default_factory=list)
2109
2148
  dash_icon: str | None = None
2149
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2110
2150
  hide_copyright: bool | None = None
2111
2151
  config_keys: list[str] = Field(default_factory=list)
2112
2152
  dash_global_config_keys: list[str] = Field(default_factory=list)
@@ -2120,6 +2160,7 @@ class PortalGetResponse(StrictModel):
2120
2160
  dash_name: str | None = None
2121
2161
  package_tag_ids: list[int] = Field(default_factory=list)
2122
2162
  dash_icon: str | None = None
2163
+ icon_config: dict[str, Any] = Field(default_factory=dict)
2123
2164
  hide_copyright: bool | None = None
2124
2165
  visibility: dict[str, Any] = Field(default_factory=dict)
2125
2166
  auth: dict[str, Any] = Field(default_factory=dict)