@qingflow-tech/qingflow-app-user-mcp 1.0.39 → 1.0.41
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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +18 -7
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +59 -0
- package/skills/qingflow-app-builder/references/create-app.md +13 -7
- package/skills/qingflow-app-builder/references/gotchas.md +6 -0
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +47 -0
- package/skills/qingflow-app-builder/references/solution-playbooks.md +10 -0
- package/skills/qingflow-app-builder/references/tool-selection.md +2 -2
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +2 -0
- package/skills/qingflow-app-user/SKILL.md +2 -0
- package/skills/qingflow-mcp-setup/SKILL.md +2 -0
- package/skills/qingflow-record-analysis/SKILL.md +3 -1
- package/skills/qingflow-record-delete/SKILL.md +2 -0
- package/skills/qingflow-record-import/SKILL.md +29 -0
- package/skills/qingflow-record-insert/SKILL.md +24 -1
- package/skills/qingflow-record-update/SKILL.md +3 -0
- package/skills/qingflow-task-ops/SKILL.md +2 -0
- package/src/qingflow_mcp/builder_facade/models.py +183 -0
- package/src/qingflow_mcp/builder_facade/service.py +722 -74
- package/src/qingflow_mcp/cli/commands/builder.py +62 -2
- package/src/qingflow_mcp/cli/commands/common.py +12 -3
- package/src/qingflow_mcp/cli/formatters.py +1 -0
- package/src/qingflow_mcp/cli/main.py +2 -0
- package/src/qingflow_mcp/response_trim.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +515 -22
- package/src/qingflow_mcp/tools/record_tools.py +28 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -70,17 +71,36 @@ class PublicExternalVisibilityMode(str, Enum):
|
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
FIELD_TYPE_ALIASES: dict[str, PublicFieldType] = {
|
|
74
|
+
"multiline": PublicFieldType.long_text,
|
|
75
|
+
"multiline_text": PublicFieldType.long_text,
|
|
76
|
+
"multi_line": PublicFieldType.long_text,
|
|
77
|
+
"multi_line_text": PublicFieldType.long_text,
|
|
73
78
|
"textarea": PublicFieldType.long_text,
|
|
79
|
+
"longtext": PublicFieldType.long_text,
|
|
80
|
+
"long-text": PublicFieldType.long_text,
|
|
74
81
|
"amount": PublicFieldType.amount,
|
|
75
82
|
"currency": PublicFieldType.amount,
|
|
76
83
|
"mobile": PublicFieldType.phone,
|
|
77
84
|
"user": PublicFieldType.member,
|
|
78
85
|
"users": PublicFieldType.member,
|
|
79
86
|
"select": PublicFieldType.single_select,
|
|
87
|
+
"single_choice": PublicFieldType.single_select,
|
|
88
|
+
"single-choice": PublicFieldType.single_select,
|
|
89
|
+
"single choice": PublicFieldType.single_select,
|
|
90
|
+
"choice": PublicFieldType.single_select,
|
|
91
|
+
"dropdown": PublicFieldType.single_select,
|
|
80
92
|
"radio": PublicFieldType.single_select,
|
|
81
93
|
"checkbox": PublicFieldType.multi_select,
|
|
82
94
|
"multi_select": PublicFieldType.multi_select,
|
|
83
95
|
"multi-select": PublicFieldType.multi_select,
|
|
96
|
+
"multi select": PublicFieldType.multi_select,
|
|
97
|
+
"multiselect": PublicFieldType.multi_select,
|
|
98
|
+
"multi_choice": PublicFieldType.multi_select,
|
|
99
|
+
"multi-choice": PublicFieldType.multi_select,
|
|
100
|
+
"multi choice": PublicFieldType.multi_select,
|
|
101
|
+
"multiple_choice": PublicFieldType.multi_select,
|
|
102
|
+
"multiple-choice": PublicFieldType.multi_select,
|
|
103
|
+
"multiple choice": PublicFieldType.multi_select,
|
|
84
104
|
"departments": PublicFieldType.department,
|
|
85
105
|
"qlinker": PublicFieldType.q_linker,
|
|
86
106
|
"q_linker": PublicFieldType.q_linker,
|
|
@@ -1796,6 +1816,65 @@ class ChartFilterRulePatch(StrictModel):
|
|
|
1796
1816
|
return self
|
|
1797
1817
|
|
|
1798
1818
|
|
|
1819
|
+
class ChartMetricPatch(StrictModel):
|
|
1820
|
+
op: str = "count"
|
|
1821
|
+
field_name: str | None = Field(default=None, validation_alias=AliasChoices("field", "field_name", "fieldName", "name"))
|
|
1822
|
+
alias: str | None = None
|
|
1823
|
+
|
|
1824
|
+
@model_validator(mode="before")
|
|
1825
|
+
@classmethod
|
|
1826
|
+
def normalize_metric(cls, value: Any) -> Any:
|
|
1827
|
+
if isinstance(value, str):
|
|
1828
|
+
raw = value.strip()
|
|
1829
|
+
match = re.fullmatch(r"([A-Za-z_][A-Za-z0-9_]*)\s*\(\s*(.*?)\s*\)", raw)
|
|
1830
|
+
if match:
|
|
1831
|
+
op = match.group(1).strip().lower()
|
|
1832
|
+
field = match.group(2).strip()
|
|
1833
|
+
payload: dict[str, Any] = {"op": op}
|
|
1834
|
+
if field and field != "*":
|
|
1835
|
+
payload["field_name"] = field
|
|
1836
|
+
return payload
|
|
1837
|
+
if raw:
|
|
1838
|
+
return {"op": "sum", "field_name": raw}
|
|
1839
|
+
return {"op": "count"}
|
|
1840
|
+
if not isinstance(value, dict):
|
|
1841
|
+
return value
|
|
1842
|
+
payload = dict(value)
|
|
1843
|
+
if "operation" in payload and "op" not in payload:
|
|
1844
|
+
payload["op"] = payload.pop("operation")
|
|
1845
|
+
if "agg" in payload and "op" not in payload:
|
|
1846
|
+
payload["op"] = payload.pop("agg")
|
|
1847
|
+
if "aggregate" in payload and "op" not in payload:
|
|
1848
|
+
payload["op"] = payload.pop("aggregate")
|
|
1849
|
+
if "aggregation" in payload and "op" not in payload:
|
|
1850
|
+
payload["op"] = payload.pop("aggregation")
|
|
1851
|
+
return payload
|
|
1852
|
+
|
|
1853
|
+
@model_validator(mode="after")
|
|
1854
|
+
def validate_metric(self) -> "ChartMetricPatch":
|
|
1855
|
+
normalized_op = str(self.op or "count").strip().lower()
|
|
1856
|
+
op_aliases = {
|
|
1857
|
+
"average": "avg",
|
|
1858
|
+
"mean": "avg",
|
|
1859
|
+
"total": "sum",
|
|
1860
|
+
"cnt": "count",
|
|
1861
|
+
"count_all": "count",
|
|
1862
|
+
}
|
|
1863
|
+
self.op = op_aliases.get(normalized_op, normalized_op)
|
|
1864
|
+
if self.field_name is not None:
|
|
1865
|
+
field_name = str(self.field_name).strip()
|
|
1866
|
+
self.field_name = field_name or None
|
|
1867
|
+
if self.alias is not None:
|
|
1868
|
+
alias = str(self.alias).strip()
|
|
1869
|
+
self.alias = alias or None
|
|
1870
|
+
supported = {"count", "sum", "avg", "max", "min"}
|
|
1871
|
+
if self.op not in supported:
|
|
1872
|
+
raise ValueError(f"chart metric op must be one of {sorted(supported)}")
|
|
1873
|
+
if self.op != "count" and not self.field_name:
|
|
1874
|
+
raise ValueError(f"chart metric op '{self.op}' requires field")
|
|
1875
|
+
return self
|
|
1876
|
+
|
|
1877
|
+
|
|
1799
1878
|
class ChartUpsertPatch(StrictModel):
|
|
1800
1879
|
chart_id: str | None = None
|
|
1801
1880
|
name: str
|
|
@@ -1803,6 +1882,17 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1803
1882
|
dimension_field_ids: list[str] = Field(default_factory=list)
|
|
1804
1883
|
indicator_field_ids: list[str] = Field(default_factory=list)
|
|
1805
1884
|
filters: list[ChartFilterRulePatch] = Field(default_factory=list)
|
|
1885
|
+
group_by: list[str] = Field(default_factory=list)
|
|
1886
|
+
rows: list[str] = Field(default_factory=list)
|
|
1887
|
+
columns: list[str] = Field(default_factory=list)
|
|
1888
|
+
metric: ChartMetricPatch | None = None
|
|
1889
|
+
metrics: list[ChartMetricPatch] = Field(default_factory=list)
|
|
1890
|
+
x_metric: ChartMetricPatch | None = None
|
|
1891
|
+
y_metric: ChartMetricPatch | None = None
|
|
1892
|
+
left_metric: ChartMetricPatch | None = None
|
|
1893
|
+
right_metric: ChartMetricPatch | None = None
|
|
1894
|
+
value_metric: ChartMetricPatch | None = None
|
|
1895
|
+
target_metric: ChartMetricPatch | None = None
|
|
1806
1896
|
question_config: list[dict[str, Any]] = Field(default_factory=list)
|
|
1807
1897
|
user_config: list[dict[str, Any]] = Field(default_factory=list)
|
|
1808
1898
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -1820,10 +1910,74 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1820
1910
|
payload["chart_type"] = payload.pop("type")
|
|
1821
1911
|
if "dimension_fields" in payload and "dimension_field_ids" not in payload:
|
|
1822
1912
|
payload["dimension_field_ids"] = payload.pop("dimension_fields")
|
|
1913
|
+
if "dimensions" in payload and "dimension_field_ids" not in payload and "group_by" not in payload:
|
|
1914
|
+
payload["group_by"] = payload.pop("dimensions")
|
|
1915
|
+
if "groupBy" in payload and "group_by" not in payload:
|
|
1916
|
+
payload["group_by"] = payload.pop("groupBy")
|
|
1917
|
+
if "where" in payload and "filters" not in payload:
|
|
1918
|
+
payload["filters"] = payload.pop("where")
|
|
1919
|
+
if "filter_rules" in payload and "filters" not in payload:
|
|
1920
|
+
payload["filters"] = payload.pop("filter_rules")
|
|
1921
|
+
if "filterRules" in payload and "filters" not in payload:
|
|
1922
|
+
payload["filters"] = payload.pop("filterRules")
|
|
1823
1923
|
if "indicator_fields" in payload and "indicator_field_ids" not in payload:
|
|
1824
1924
|
payload["indicator_field_ids"] = payload.pop("indicator_fields")
|
|
1825
1925
|
if "metric_field_ids" in payload and "indicator_field_ids" not in payload:
|
|
1826
1926
|
payload["indicator_field_ids"] = payload.pop("metric_field_ids")
|
|
1927
|
+
metric_slots: list[Any] = []
|
|
1928
|
+
generic_metric_keys = ("metric", "metrics")
|
|
1929
|
+
axis_metric_keys = (
|
|
1930
|
+
"x_metric",
|
|
1931
|
+
"xMetric",
|
|
1932
|
+
"y_metric",
|
|
1933
|
+
"yMetric",
|
|
1934
|
+
"left_metric",
|
|
1935
|
+
"leftMetric",
|
|
1936
|
+
"right_metric",
|
|
1937
|
+
"rightMetric",
|
|
1938
|
+
"value_metric",
|
|
1939
|
+
"valueMetric",
|
|
1940
|
+
"target_metric",
|
|
1941
|
+
"targetMetric",
|
|
1942
|
+
)
|
|
1943
|
+
|
|
1944
|
+
def has_metric_value(key: str) -> bool:
|
|
1945
|
+
entry = payload.get(key)
|
|
1946
|
+
return entry is not None and entry != "" and entry != []
|
|
1947
|
+
|
|
1948
|
+
if any(has_metric_value(key) for key in generic_metric_keys) and any(has_metric_value(key) for key in axis_metric_keys):
|
|
1949
|
+
raise ValueError(
|
|
1950
|
+
"chart metric input is ambiguous: use either metric/metrics or axis-specific "
|
|
1951
|
+
"x_metric/y_metric, left_metric/right_metric, value_metric/target_metric, not both"
|
|
1952
|
+
)
|
|
1953
|
+
if "metric" in payload and "metrics" not in payload:
|
|
1954
|
+
payload["metrics"] = [payload["metric"]]
|
|
1955
|
+
for key in ("x_metric", "xMetric", "left_metric", "leftMetric", "value_metric", "valueMetric"):
|
|
1956
|
+
if key in payload:
|
|
1957
|
+
slot_value = payload.get(key)
|
|
1958
|
+
if slot_value is not None:
|
|
1959
|
+
metric_slots.append(slot_value)
|
|
1960
|
+
canonical = {
|
|
1961
|
+
"xMetric": "x_metric",
|
|
1962
|
+
"leftMetric": "left_metric",
|
|
1963
|
+
"valueMetric": "value_metric",
|
|
1964
|
+
}.get(key)
|
|
1965
|
+
if canonical and canonical not in payload:
|
|
1966
|
+
payload[canonical] = payload.pop(key)
|
|
1967
|
+
for key in ("y_metric", "yMetric", "right_metric", "rightMetric", "target_metric", "targetMetric"):
|
|
1968
|
+
if key in payload:
|
|
1969
|
+
slot_value = payload.get(key)
|
|
1970
|
+
if slot_value is not None:
|
|
1971
|
+
metric_slots.append(slot_value)
|
|
1972
|
+
canonical = {
|
|
1973
|
+
"yMetric": "y_metric",
|
|
1974
|
+
"rightMetric": "right_metric",
|
|
1975
|
+
"targetMetric": "target_metric",
|
|
1976
|
+
}.get(key)
|
|
1977
|
+
if canonical and canonical not in payload:
|
|
1978
|
+
payload[canonical] = payload.pop(key)
|
|
1979
|
+
if metric_slots and "metrics" not in payload:
|
|
1980
|
+
payload["metrics"] = metric_slots
|
|
1827
1981
|
raw_type = payload.get("chart_type")
|
|
1828
1982
|
if isinstance(raw_type, str):
|
|
1829
1983
|
normalized = raw_type.strip().lower()
|
|
@@ -1850,8 +2004,31 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1850
2004
|
payload["dimension_field_ids"] = [str(item) for item in payload["dimension_field_ids"] if item is not None and str(item).strip()]
|
|
1851
2005
|
if isinstance(payload.get("indicator_field_ids"), list):
|
|
1852
2006
|
payload["indicator_field_ids"] = [str(item) for item in payload["indicator_field_ids"] if item is not None and str(item).strip()]
|
|
2007
|
+
for key in ("group_by", "rows", "columns"):
|
|
2008
|
+
if isinstance(payload.get(key), str):
|
|
2009
|
+
payload[key] = [payload[key]]
|
|
2010
|
+
if isinstance(payload.get(key), list):
|
|
2011
|
+
payload[key] = [str(item) for item in payload[key] if item is not None and str(item).strip()]
|
|
1853
2012
|
return payload
|
|
1854
2013
|
|
|
2014
|
+
@model_validator(mode="after")
|
|
2015
|
+
def apply_semantic_chart_fields(self) -> "ChartUpsertPatch":
|
|
2016
|
+
if self.group_by and not self.dimension_field_ids:
|
|
2017
|
+
self.dimension_field_ids = list(self.group_by)
|
|
2018
|
+
if self.rows and not self.dimension_field_ids:
|
|
2019
|
+
self.dimension_field_ids = list(self.rows)
|
|
2020
|
+
semantic_metrics: list[ChartMetricPatch] = []
|
|
2021
|
+
if self.metrics:
|
|
2022
|
+
semantic_metrics.extend(self.metrics)
|
|
2023
|
+
for metric in (self.x_metric, self.y_metric, self.left_metric, self.right_metric, self.value_metric, self.target_metric):
|
|
2024
|
+
if metric is not None and metric not in semantic_metrics:
|
|
2025
|
+
semantic_metrics.append(metric)
|
|
2026
|
+
if semantic_metrics and not self.metrics:
|
|
2027
|
+
self.metrics = semantic_metrics
|
|
2028
|
+
if self.metrics and not self.metric:
|
|
2029
|
+
self.metric = self.metrics[0]
|
|
2030
|
+
return self
|
|
2031
|
+
|
|
1855
2032
|
|
|
1856
2033
|
class ChartPartialPatch(StrictModel):
|
|
1857
2034
|
chart_id: str | None = None
|
|
@@ -1993,6 +2170,7 @@ class PortalViewRefPatch(StrictModel):
|
|
|
1993
2170
|
class PortalSectionPatch(StrictModel):
|
|
1994
2171
|
title: str
|
|
1995
2172
|
source_type: str = Field(validation_alias=AliasChoices("source_type", "sourceType"))
|
|
2173
|
+
role: str | None = Field(default=None, validation_alias=AliasChoices("role", "zone", "section_role", "sectionRole"))
|
|
1996
2174
|
position: PortalComponentPositionPatch | None = None
|
|
1997
2175
|
dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
|
|
1998
2176
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -2010,6 +2188,9 @@ class PortalSectionPatch(StrictModel):
|
|
|
2010
2188
|
raw_type = payload.get("source_type", payload.get("sourceType"))
|
|
2011
2189
|
if isinstance(raw_type, str):
|
|
2012
2190
|
payload["source_type"] = raw_type.strip().lower()
|
|
2191
|
+
raw_role = payload.get("role", payload.get("zone", payload.get("section_role", payload.get("sectionRole"))))
|
|
2192
|
+
if isinstance(raw_role, str):
|
|
2193
|
+
payload["role"] = raw_role.strip().lower()
|
|
2013
2194
|
if "chartRef" in payload and "chart_ref" not in payload:
|
|
2014
2195
|
payload["chart_ref"] = payload.pop("chartRef")
|
|
2015
2196
|
if "viewRef" in payload and "view_ref" not in payload:
|
|
@@ -2216,6 +2397,8 @@ class ChartGetResponse(StrictModel):
|
|
|
2216
2397
|
base: dict[str, Any] = Field(default_factory=dict)
|
|
2217
2398
|
visibility: dict[str, Any] = Field(default_factory=dict)
|
|
2218
2399
|
filters: list[list[dict[str, Any]]] = Field(default_factory=list)
|
|
2400
|
+
group_by: list[str] = Field(default_factory=list)
|
|
2401
|
+
metrics: list[dict[str, Any]] = Field(default_factory=list)
|
|
2219
2402
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2220
2403
|
|
|
2221
2404
|
|