@josephyan/qingflow-app-user-mcp 1.0.5 → 1.0.6
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@1.0.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@1.0.6
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@1.0.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@1.0.6 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -75,7 +75,7 @@ Use `record_access` to fetch detail rows into local CSV shards. It does not anal
|
|
|
75
75
|
|
|
76
76
|
Then run Python against every `files[].local_path`. CSV columns are stable: `record_id`, then `field_<field_id>`. Use `fields[]` metadata to map titles and types.
|
|
77
77
|
|
|
78
|
-
- Never ask for `page`, `page_size`, `limit`, or `max_rows`; `record_access` owns paging internally.
|
|
78
|
+
- Never ask for `page`, `page_size`, `limit`, or `max_rows`; `record_access` owns paging internally and follows the backend's native paging capability.
|
|
79
79
|
- If multiple CSV files are returned, read them all.
|
|
80
80
|
- If `complete=false` or `safe_for_final_conclusion=false`, downgrade the answer and disclose the limitation.
|
|
81
81
|
- `record_export_direct` is only for explicit export/download/Excel requests, not default analysis.
|
|
@@ -89,7 +89,7 @@ Examples of the right recovery question:
|
|
|
89
89
|
|
|
90
90
|
## Do not try to control paging manually
|
|
91
91
|
|
|
92
|
-
`record_access` and `record_analyze`
|
|
92
|
+
`record_access` hides paging and follows the backend's native paging capability. `record_analyze` hides paging and scan budget on purpose.
|
|
93
93
|
|
|
94
94
|
- Do not invent `page_size`
|
|
95
95
|
- Do not invent `requested_pages`
|
|
@@ -32,9 +32,8 @@ from .directory_tools import _directory_has_more, _directory_items
|
|
|
32
32
|
|
|
33
33
|
DEFAULT_QUERY_PAGE_SIZE = 50
|
|
34
34
|
DEFAULT_LIST_PAGE_SIZE = 200
|
|
35
|
+
BACKEND_RECORD_ACCESS_PAGE_SIZE = 1000
|
|
35
36
|
DEFAULT_RECORD_ACCESS_SHARD_ROWS = 5000
|
|
36
|
-
DEFAULT_RECORD_ACCESS_HARD_ROWS = 50000
|
|
37
|
-
DEFAULT_RECORD_ACCESS_TIMEOUT_SECONDS = 60
|
|
38
37
|
DEFAULT_ANALYSIS_PAGE_SIZE = 1000
|
|
39
38
|
DEFAULT_SCAN_MAX_PAGES = 10
|
|
40
39
|
DEFAULT_ANALYSIS_SCAN_MAX_PAGES = 100
|
|
@@ -1870,7 +1869,6 @@ class RecordTools(ToolBase):
|
|
|
1870
1869
|
primary_search_que_ids = primary_search_que_ids or None
|
|
1871
1870
|
match_rules = self._resolve_match_rules(context, filters, index)
|
|
1872
1871
|
sort_rules = self._resolve_sorts(sorts, index)
|
|
1873
|
-
dept_member_cache: dict[int, set[int]] = {}
|
|
1874
1872
|
used_list_type: int | None = None
|
|
1875
1873
|
fallback_list_types = (
|
|
1876
1874
|
[view_route.list_type if view_route.list_type is not None else DEFAULT_RECORD_LIST_TYPE]
|
|
@@ -1934,17 +1932,8 @@ class RecordTools(ToolBase):
|
|
|
1934
1932
|
current_page = 1
|
|
1935
1933
|
has_more = False
|
|
1936
1934
|
reported_total: int | None = None
|
|
1937
|
-
limit_reached = False
|
|
1938
|
-
timeout_reached = False
|
|
1939
|
-
started_at = time.perf_counter()
|
|
1940
1935
|
try:
|
|
1941
1936
|
while True:
|
|
1942
|
-
if row_count >= DEFAULT_RECORD_ACCESS_HARD_ROWS:
|
|
1943
|
-
limit_reached = True
|
|
1944
|
-
break
|
|
1945
|
-
if time.perf_counter() - started_at >= DEFAULT_RECORD_ACCESS_TIMEOUT_SECONDS:
|
|
1946
|
-
timeout_reached = True
|
|
1947
|
-
break
|
|
1948
1937
|
if used_list_type is None:
|
|
1949
1938
|
last_error: QingflowApiError | None = None
|
|
1950
1939
|
page: JSONObject | None = None
|
|
@@ -1955,7 +1944,7 @@ class RecordTools(ToolBase):
|
|
|
1955
1944
|
app_key=app_key,
|
|
1956
1945
|
view_selection=view_selection,
|
|
1957
1946
|
page_num=current_page,
|
|
1958
|
-
page_size=
|
|
1947
|
+
page_size=BACKEND_RECORD_ACCESS_PAGE_SIZE,
|
|
1959
1948
|
query_key=None,
|
|
1960
1949
|
match_rules=match_rules,
|
|
1961
1950
|
sorts=sort_rules,
|
|
@@ -1982,7 +1971,7 @@ class RecordTools(ToolBase):
|
|
|
1982
1971
|
app_key=app_key,
|
|
1983
1972
|
view_selection=view_selection,
|
|
1984
1973
|
page_num=current_page,
|
|
1985
|
-
page_size=
|
|
1974
|
+
page_size=BACKEND_RECORD_ACCESS_PAGE_SIZE,
|
|
1986
1975
|
query_key=None,
|
|
1987
1976
|
match_rules=match_rules,
|
|
1988
1977
|
sorts=sort_rules,
|
|
@@ -1992,25 +1981,15 @@ class RecordTools(ToolBase):
|
|
|
1992
1981
|
page_rows = page.get("list")
|
|
1993
1982
|
items = page_rows if isinstance(page_rows, list) else []
|
|
1994
1983
|
if reported_total is None:
|
|
1995
|
-
reported_total = _effective_total(page,
|
|
1996
|
-
has_more = _page_has_more(page, current_page,
|
|
1984
|
+
reported_total = _effective_total(page, BACKEND_RECORD_ACCESS_PAGE_SIZE)
|
|
1985
|
+
has_more = _page_has_more(page, current_page, BACKEND_RECORD_ACCESS_PAGE_SIZE, len(items))
|
|
1997
1986
|
page_apply_order: list[int] = []
|
|
1998
1987
|
page_answer_map: dict[int, list[JSONValue]] = {}
|
|
1999
1988
|
for item in items:
|
|
2000
|
-
if row_count + len(page_apply_order) >= DEFAULT_RECORD_ACCESS_HARD_ROWS:
|
|
2001
|
-
limit_reached = True
|
|
2002
|
-
break
|
|
2003
1989
|
if not isinstance(item, dict):
|
|
2004
1990
|
continue
|
|
2005
1991
|
answers = item.get("answers")
|
|
2006
1992
|
answer_list = answers if isinstance(answers, list) else []
|
|
2007
|
-
if not self._matches_view_selection(
|
|
2008
|
-
context,
|
|
2009
|
-
answer_list,
|
|
2010
|
-
view_selection=view_selection,
|
|
2011
|
-
dept_member_cache=dept_member_cache,
|
|
2012
|
-
):
|
|
2013
|
-
continue
|
|
2014
1993
|
apply_id = _coerce_count(item.get("applyId")) or _coerce_count(item.get("id"))
|
|
2015
1994
|
if apply_id is None:
|
|
2016
1995
|
continue
|
|
@@ -2023,7 +2002,7 @@ class RecordTools(ToolBase):
|
|
|
2023
2002
|
app_key=app_key,
|
|
2024
2003
|
view_selection=view_selection,
|
|
2025
2004
|
page_num=current_page,
|
|
2026
|
-
page_size=
|
|
2005
|
+
page_size=BACKEND_RECORD_ACCESS_PAGE_SIZE,
|
|
2027
2006
|
query_key=None,
|
|
2028
2007
|
match_rules=match_rules,
|
|
2029
2008
|
sorts=sort_rules,
|
|
@@ -2045,11 +2024,8 @@ class RecordTools(ToolBase):
|
|
|
2045
2024
|
cast(list[JSONValue], extra_answer_list),
|
|
2046
2025
|
)
|
|
2047
2026
|
for apply_id in page_apply_order:
|
|
2048
|
-
if row_count >= DEFAULT_RECORD_ACCESS_HARD_ROWS:
|
|
2049
|
-
limit_reached = True
|
|
2050
|
-
break
|
|
2051
2027
|
write_record(apply_id, page_answer_map.get(apply_id, []))
|
|
2052
|
-
if
|
|
2028
|
+
if not has_more:
|
|
2053
2029
|
break
|
|
2054
2030
|
current_page += 1
|
|
2055
2031
|
finally:
|
|
@@ -2069,21 +2045,7 @@ class RecordTools(ToolBase):
|
|
|
2069
2045
|
),
|
|
2070
2046
|
}
|
|
2071
2047
|
)
|
|
2072
|
-
|
|
2073
|
-
warnings.append(
|
|
2074
|
-
{
|
|
2075
|
-
"code": "RECORD_ACCESS_INTERNAL_LIMIT_REACHED",
|
|
2076
|
-
"message": "record_access reached its internal row limit before all pages were fetched.",
|
|
2077
|
-
}
|
|
2078
|
-
)
|
|
2079
|
-
if timeout_reached:
|
|
2080
|
-
warnings.append(
|
|
2081
|
-
{
|
|
2082
|
-
"code": "RECORD_ACCESS_TIMEOUT",
|
|
2083
|
-
"message": "record_access reached its internal time limit before all pages were fetched.",
|
|
2084
|
-
}
|
|
2085
|
-
)
|
|
2086
|
-
complete = not has_more and not limit_reached and not timeout_reached
|
|
2048
|
+
complete = not has_more
|
|
2087
2049
|
safe_for_final_conclusion = complete and not any(
|
|
2088
2050
|
warning.get("code") == "CUSTOM_VIEW_FILTER_UNVERIFIED" for warning in warnings
|
|
2089
2051
|
)
|
|
@@ -5594,9 +5556,8 @@ class RecordTools(ToolBase):
|
|
|
5594
5556
|
source_pages: list[int] = []
|
|
5595
5557
|
result_amount: int | None = None
|
|
5596
5558
|
has_more = False
|
|
5597
|
-
dept_member_cache: dict[int, set[int]] = {}
|
|
5598
5559
|
view_selection = resolved_view.view_selection
|
|
5599
|
-
local_filtering = bool(filters)
|
|
5560
|
+
local_filtering = bool(filters)
|
|
5600
5561
|
group_stats: dict[tuple[tuple[str, object], ...], JSONObject] = {}
|
|
5601
5562
|
overall_metrics = self._initialize_metric_states(metrics)
|
|
5602
5563
|
matched_rows = 0
|
|
@@ -5640,13 +5601,6 @@ class RecordTools(ToolBase):
|
|
|
5640
5601
|
continue
|
|
5641
5602
|
answers = item.get("answers")
|
|
5642
5603
|
answer_list = answers if isinstance(answers, list) else []
|
|
5643
|
-
if not self._matches_view_selection(
|
|
5644
|
-
context,
|
|
5645
|
-
answer_list,
|
|
5646
|
-
view_selection=view_selection,
|
|
5647
|
-
dept_member_cache=dept_member_cache,
|
|
5648
|
-
):
|
|
5649
|
-
continue
|
|
5650
5604
|
if not self._matches_analyze_filters(answer_list, filters):
|
|
5651
5605
|
continue
|
|
5652
5606
|
matched_rows += 1
|
|
@@ -6732,7 +6686,6 @@ class RecordTools(ToolBase):
|
|
|
6732
6686
|
if view_selection is not None
|
|
6733
6687
|
else self._get_field_index(profile, context, app_key, force_refresh=False)
|
|
6734
6688
|
)
|
|
6735
|
-
dept_member_cache: dict[int, set[int]] = {}
|
|
6736
6689
|
result = self._search_page(
|
|
6737
6690
|
context,
|
|
6738
6691
|
app_key=app_key,
|
|
@@ -6747,17 +6700,7 @@ class RecordTools(ToolBase):
|
|
|
6747
6700
|
)
|
|
6748
6701
|
rows = result.get("list")
|
|
6749
6702
|
raw_rows = rows if isinstance(rows, list) else []
|
|
6750
|
-
filtered_rows = [
|
|
6751
|
-
item
|
|
6752
|
-
for item in raw_rows
|
|
6753
|
-
if isinstance(item, dict)
|
|
6754
|
-
and self._matches_view_selection(
|
|
6755
|
-
context,
|
|
6756
|
-
item.get("answers") if isinstance(item.get("answers"), list) else [],
|
|
6757
|
-
view_selection=view_selection,
|
|
6758
|
-
dept_member_cache=dept_member_cache,
|
|
6759
|
-
)
|
|
6760
|
-
]
|
|
6703
|
+
filtered_rows = [item for item in raw_rows if isinstance(item, dict)]
|
|
6761
6704
|
if isinstance(rows, list):
|
|
6762
6705
|
result = dict(result)
|
|
6763
6706
|
result["list"] = filtered_rows
|
|
@@ -7007,7 +6950,6 @@ class RecordTools(ToolBase):
|
|
|
7007
6950
|
def runner(session_profile, context):
|
|
7008
6951
|
index = self._get_field_index(profile, context, app_key, force_refresh=False)
|
|
7009
6952
|
view_selection = self._resolve_view_selection(profile, context, app_key, view_key=view_key, view_name=view_name)
|
|
7010
|
-
dept_member_cache: dict[int, set[int]] = {}
|
|
7011
6953
|
resolved_column_cap = _bounded_column_limit(
|
|
7012
6954
|
max_columns,
|
|
7013
6955
|
default_limit=MAX_LIST_COLUMN_LIMIT,
|
|
@@ -7113,13 +7055,6 @@ class RecordTools(ToolBase):
|
|
|
7113
7055
|
continue
|
|
7114
7056
|
answers = item.get("answers")
|
|
7115
7057
|
answer_list = answers if isinstance(answers, list) else []
|
|
7116
|
-
if not self._matches_view_selection(
|
|
7117
|
-
context,
|
|
7118
|
-
answer_list,
|
|
7119
|
-
view_selection=view_selection,
|
|
7120
|
-
dept_member_cache=dept_member_cache,
|
|
7121
|
-
):
|
|
7122
|
-
continue
|
|
7123
7058
|
matched_records += 1
|
|
7124
7059
|
apply_id = _coerce_count(item.get("applyId")) or _coerce_count(item.get("id"))
|
|
7125
7060
|
row = _build_flat_row(answer_list, selected_fields_from_primary, apply_id=apply_id)
|