@josephyan/qingflow-app-user-mcp 0.2.0-beta.83 → 0.2.0-beta.84
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/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +136 -24
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.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.84
|
|
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.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.84 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -450,6 +450,10 @@ class AiBuilderFacade:
|
|
|
450
450
|
"http_status": detail_read_error.http_status,
|
|
451
451
|
}
|
|
452
452
|
)
|
|
453
|
+
public_items = _public_package_items_from_tag_items(source.get("tagItems") or base.get("tagItems"))
|
|
454
|
+
item_count = summary.get("itemCount")
|
|
455
|
+
if not isinstance(item_count, int) or item_count < 0 or (item_count == 0 and public_items):
|
|
456
|
+
item_count = len(public_items)
|
|
453
457
|
return {
|
|
454
458
|
"status": "success",
|
|
455
459
|
"error_code": None,
|
|
@@ -469,13 +473,13 @@ class AiBuilderFacade:
|
|
|
469
473
|
"package_name": str(source.get("tagName") or base.get("tagName") or "").strip() or None,
|
|
470
474
|
"icon": str(source.get("tagIcon") or base.get("tagIcon") or "").strip() or None,
|
|
471
475
|
"publish_status": source.get("publishStatus") if source.get("publishStatus") is not None else base.get("publishStatus"),
|
|
472
|
-
"item_count":
|
|
476
|
+
"item_count": item_count,
|
|
473
477
|
"add_app_status": base.get("addAppStatus") if base.get("addAppStatus") is not None else summary.get("addAppStatus"),
|
|
474
478
|
"edit_app_status": base.get("editAppStatus") if base.get("editAppStatus") is not None else summary.get("editAppStatus"),
|
|
475
479
|
"del_app_status": base.get("delAppStatus") if base.get("delAppStatus") is not None else summary.get("delAppStatus"),
|
|
476
480
|
"edit_tag_status": base.get("editTagStatus") if base.get("editTagStatus") is not None else summary.get("editTagStatus"),
|
|
477
481
|
"visibility": _public_visibility_from_member_auth(base.get("auth") or source.get("auth")),
|
|
478
|
-
"items":
|
|
482
|
+
"items": public_items,
|
|
479
483
|
}
|
|
480
484
|
|
|
481
485
|
def package_apply(
|
|
@@ -875,8 +879,19 @@ class AiBuilderFacade:
|
|
|
875
879
|
)
|
|
876
880
|
current_items = raw_tag_items
|
|
877
881
|
|
|
882
|
+
current_group_specs = _collect_backend_package_group_specs(current_items)
|
|
883
|
+
normalized_items, group_resolution_issues = _align_public_package_group_ids(items, current_group_specs=current_group_specs)
|
|
884
|
+
if group_resolution_issues:
|
|
885
|
+
return _failed(
|
|
886
|
+
"PACKAGE_GROUP_AMBIGUOUS",
|
|
887
|
+
"items contains group names that match multiple existing package groups; pass explicit group_id to disambiguate",
|
|
888
|
+
normalized_args=normalized_args,
|
|
889
|
+
details={"package_id": package_id, "issues": group_resolution_issues},
|
|
890
|
+
suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
|
|
891
|
+
)
|
|
892
|
+
|
|
878
893
|
current_resources = _flatten_package_resource_identities(current_items, public=False)
|
|
879
|
-
desired_resources = _flatten_package_resource_identities(
|
|
894
|
+
desired_resources = _flatten_package_resource_identities(normalized_items, public=True)
|
|
880
895
|
missing_resources = sorted(current_resources - desired_resources)
|
|
881
896
|
if missing_resources and not allow_detach:
|
|
882
897
|
return _failed(
|
|
@@ -887,7 +902,7 @@ class AiBuilderFacade:
|
|
|
887
902
|
suggested_next_call=None,
|
|
888
903
|
)
|
|
889
904
|
|
|
890
|
-
duplicate_resources = _find_duplicate_package_resources(
|
|
905
|
+
duplicate_resources = _find_duplicate_package_resources(normalized_items)
|
|
891
906
|
if duplicate_resources:
|
|
892
907
|
return _failed(
|
|
893
908
|
"PACKAGE_LAYOUT_DUPLICATE_ITEM",
|
|
@@ -897,8 +912,8 @@ class AiBuilderFacade:
|
|
|
897
912
|
suggested_next_call=None,
|
|
898
913
|
)
|
|
899
914
|
|
|
900
|
-
current_groups =
|
|
901
|
-
desired_groups = _collect_public_package_group_specs(
|
|
915
|
+
current_groups = {int(spec["group_id"]): str(spec["name"] or "").strip() for spec in current_group_specs}
|
|
916
|
+
desired_groups = _collect_public_package_group_specs(normalized_items)
|
|
902
917
|
desired_group_ids = {
|
|
903
918
|
group_id for group_id in (_coerce_positive_int(group.get("group_id")) for group in desired_groups) if group_id is not None
|
|
904
919
|
}
|
|
@@ -907,7 +922,7 @@ class AiBuilderFacade:
|
|
|
907
922
|
permission_outcomes: list[PermissionCheckOutcome] = []
|
|
908
923
|
needs_group_create = any(_coerce_positive_int(group.get("group_id")) is None for group in desired_groups)
|
|
909
924
|
needs_group_delete = bool(deleted_group_ids)
|
|
910
|
-
needs_edit_app = bool(
|
|
925
|
+
needs_edit_app = bool(normalized_items)
|
|
911
926
|
for required_permission in (
|
|
912
927
|
(["add_app"] if needs_group_create else [])
|
|
913
928
|
+ (["edit_app"] if needs_edit_app else [])
|
|
@@ -975,7 +990,7 @@ class AiBuilderFacade:
|
|
|
975
990
|
group_ids_by_path[path] = group_id
|
|
976
991
|
|
|
977
992
|
try:
|
|
978
|
-
backend_items = _backend_package_items_from_public_items(
|
|
993
|
+
backend_items = _backend_package_items_from_public_items(normalized_items, group_ids_by_path)
|
|
979
994
|
except ValueError as error:
|
|
980
995
|
return _failed(
|
|
981
996
|
"PACKAGE_LAYOUT_INVALID",
|
|
@@ -984,8 +999,6 @@ class AiBuilderFacade:
|
|
|
984
999
|
details={"package_id": package_id},
|
|
985
1000
|
suggested_next_call=None,
|
|
986
1001
|
)
|
|
987
|
-
for group_id in deleted_group_ids:
|
|
988
|
-
backend_items.append({"itemType": 3, "groupId": group_id, "title": current_groups.get(group_id) or "", "subItems": []})
|
|
989
1002
|
|
|
990
1003
|
try:
|
|
991
1004
|
sort_result = self.packages.package_sort_items(profile=profile, tag_id=package_id, tag_items=backend_items)
|
|
@@ -12419,22 +12432,36 @@ def _find_duplicate_package_resources(items: Any) -> list[tuple[str, str]]:
|
|
|
12419
12432
|
|
|
12420
12433
|
|
|
12421
12434
|
def _collect_backend_package_groups(tag_items: Any) -> dict[int, str]:
|
|
12422
|
-
|
|
12435
|
+
return {
|
|
12436
|
+
int(spec["group_id"]): str(spec["name"] or "").strip()
|
|
12437
|
+
for spec in _collect_backend_package_group_specs(tag_items)
|
|
12438
|
+
if _coerce_positive_int(spec.get("group_id")) is not None
|
|
12439
|
+
}
|
|
12423
12440
|
|
|
12424
|
-
def walk(value: Any) -> None:
|
|
12425
|
-
if isinstance(value, list):
|
|
12426
|
-
for child in value:
|
|
12427
|
-
walk(child)
|
|
12428
|
-
return
|
|
12429
|
-
if not isinstance(value, dict):
|
|
12430
|
-
return
|
|
12431
|
-
if _coerce_positive_int(value.get("itemType")) == 3:
|
|
12432
|
-
group_id = _coerce_positive_int(value.get("groupId"))
|
|
12433
|
-
if group_id is not None:
|
|
12434
|
-
groups[group_id] = str(value.get("title") or value.get("groupName") or "").strip()
|
|
12435
|
-
walk(value.get("subItems"))
|
|
12436
12441
|
|
|
12437
|
-
|
|
12442
|
+
def _collect_backend_package_group_specs(tag_items: Any, *, path: tuple[int, ...] = ()) -> list[JSONObject]:
|
|
12443
|
+
if not isinstance(tag_items, list):
|
|
12444
|
+
return []
|
|
12445
|
+
groups: list[JSONObject] = []
|
|
12446
|
+
for index, item in enumerate(tag_items):
|
|
12447
|
+
if not isinstance(item, dict):
|
|
12448
|
+
continue
|
|
12449
|
+
item_type = _coerce_positive_int(item.get("itemType"))
|
|
12450
|
+
child_path = (*path, index)
|
|
12451
|
+
if item_type == 3:
|
|
12452
|
+
group_id = _coerce_positive_int(item.get("groupId"))
|
|
12453
|
+
if group_id is None:
|
|
12454
|
+
continue
|
|
12455
|
+
child_items = item.get("subItems") if isinstance(item.get("subItems"), list) else []
|
|
12456
|
+
groups.append(
|
|
12457
|
+
{
|
|
12458
|
+
"path": list(child_path),
|
|
12459
|
+
"group_id": group_id,
|
|
12460
|
+
"name": str(item.get("title") or item.get("groupName") or "").strip(),
|
|
12461
|
+
"resource_signature": _package_resource_signature(child_items, public=False),
|
|
12462
|
+
}
|
|
12463
|
+
)
|
|
12464
|
+
groups.extend(_collect_backend_package_group_specs(child_items, path=child_path))
|
|
12438
12465
|
return groups
|
|
12439
12466
|
|
|
12440
12467
|
|
|
@@ -12459,6 +12486,91 @@ def _collect_public_package_group_specs(items: Any, *, path: tuple[int, ...] = (
|
|
|
12459
12486
|
return groups
|
|
12460
12487
|
|
|
12461
12488
|
|
|
12489
|
+
def _align_public_package_group_ids(
|
|
12490
|
+
items: list[dict[str, Any]],
|
|
12491
|
+
*,
|
|
12492
|
+
current_group_specs: list[JSONObject],
|
|
12493
|
+
) -> tuple[list[dict[str, Any]], list[JSONObject]]:
|
|
12494
|
+
normalized_items = deepcopy(items)
|
|
12495
|
+
used_group_ids: set[int] = set()
|
|
12496
|
+
issues: list[JSONObject] = []
|
|
12497
|
+
|
|
12498
|
+
def walk(nodes: list[dict[str, Any]], *, path: tuple[int, ...] = ()) -> None:
|
|
12499
|
+
for index, node in enumerate(nodes):
|
|
12500
|
+
if not isinstance(node, dict):
|
|
12501
|
+
continue
|
|
12502
|
+
item_type = str(node.get("type") or "").strip().lower()
|
|
12503
|
+
child_path = (*path, index)
|
|
12504
|
+
child_items = node.get("items") if isinstance(node.get("items"), list) else []
|
|
12505
|
+
if item_type == "group" or isinstance(node.get("items"), list):
|
|
12506
|
+
explicit_group_id = _coerce_positive_int(node.get("group_id") or node.get("groupId"))
|
|
12507
|
+
if explicit_group_id is not None:
|
|
12508
|
+
node["group_id"] = explicit_group_id
|
|
12509
|
+
used_group_ids.add(explicit_group_id)
|
|
12510
|
+
else:
|
|
12511
|
+
group_name = str(node.get("name") or node.get("title") or node.get("group_name") or "").strip()
|
|
12512
|
+
matched_group, ambiguity = _match_existing_package_group(
|
|
12513
|
+
group_name=group_name,
|
|
12514
|
+
child_items=child_items,
|
|
12515
|
+
path=child_path,
|
|
12516
|
+
current_group_specs=current_group_specs,
|
|
12517
|
+
used_group_ids=used_group_ids,
|
|
12518
|
+
)
|
|
12519
|
+
if ambiguity is not None:
|
|
12520
|
+
issues.append(ambiguity)
|
|
12521
|
+
elif matched_group is not None:
|
|
12522
|
+
matched_group_id = _coerce_positive_int(matched_group.get("group_id"))
|
|
12523
|
+
if matched_group_id is not None:
|
|
12524
|
+
node["group_id"] = matched_group_id
|
|
12525
|
+
used_group_ids.add(matched_group_id)
|
|
12526
|
+
walk(child_items, path=child_path)
|
|
12527
|
+
|
|
12528
|
+
walk(normalized_items)
|
|
12529
|
+
return normalized_items, issues
|
|
12530
|
+
|
|
12531
|
+
|
|
12532
|
+
def _match_existing_package_group(
|
|
12533
|
+
*,
|
|
12534
|
+
group_name: str,
|
|
12535
|
+
child_items: list[dict[str, Any]],
|
|
12536
|
+
path: tuple[int, ...],
|
|
12537
|
+
current_group_specs: list[JSONObject],
|
|
12538
|
+
used_group_ids: set[int],
|
|
12539
|
+
) -> tuple[JSONObject | None, JSONObject | None]:
|
|
12540
|
+
desired_signature = _package_resource_signature(child_items, public=True)
|
|
12541
|
+
candidates = [
|
|
12542
|
+
spec
|
|
12543
|
+
for spec in current_group_specs
|
|
12544
|
+
if _coerce_positive_int(spec.get("group_id")) is not None
|
|
12545
|
+
and int(spec["group_id"]) not in used_group_ids
|
|
12546
|
+
and str(spec.get("name") or "").strip() == group_name
|
|
12547
|
+
]
|
|
12548
|
+
exact_matches = [spec for spec in candidates if spec.get("resource_signature") == desired_signature]
|
|
12549
|
+
if len(exact_matches) == 1:
|
|
12550
|
+
return exact_matches[0], None
|
|
12551
|
+
path_matches = [spec for spec in candidates if tuple(spec.get("path") or ()) == path]
|
|
12552
|
+
if len(path_matches) == 1:
|
|
12553
|
+
return path_matches[0], None
|
|
12554
|
+
if len(candidates) == 1:
|
|
12555
|
+
return candidates[0], None
|
|
12556
|
+
ambiguous_matches = exact_matches if len(exact_matches) > 1 else candidates if len(candidates) > 1 else []
|
|
12557
|
+
if ambiguous_matches:
|
|
12558
|
+
return None, {
|
|
12559
|
+
"path": list(path),
|
|
12560
|
+
"name": group_name,
|
|
12561
|
+
"candidate_group_ids": [
|
|
12562
|
+
int(spec["group_id"])
|
|
12563
|
+
for spec in ambiguous_matches
|
|
12564
|
+
if _coerce_positive_int(spec.get("group_id")) is not None
|
|
12565
|
+
],
|
|
12566
|
+
}
|
|
12567
|
+
return None, None
|
|
12568
|
+
|
|
12569
|
+
|
|
12570
|
+
def _package_resource_signature(items: Any, *, public: bool) -> tuple[tuple[str, str], ...]:
|
|
12571
|
+
return tuple(sorted(_flatten_package_resource_identities(items, public=public)))
|
|
12572
|
+
|
|
12573
|
+
|
|
12462
12574
|
def _backend_package_items_from_public_items(items: list[dict[str, Any]], group_ids_by_path: dict[tuple[int, ...], int], *, path: tuple[int, ...] = ()) -> list[JSONObject]:
|
|
12463
12575
|
backend_items: list[JSONObject] = []
|
|
12464
12576
|
for index, item in enumerate(items):
|