@josephyan/qingflow-app-user-mcp 0.2.0-beta.81 → 0.2.0-beta.82

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.81
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.82
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.81 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.82 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.81",
3
+ "version": "0.2.0-beta.82",
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.0b81"
7
+ version = "0.2.0b82"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b81"
5
+ __version__ = "0.2.0b82"
@@ -542,7 +542,10 @@ class AiBuilderFacade:
542
542
  "warnings": [],
543
543
  "verification": {
544
544
  "package_exists": True,
545
- "visibility_verified": verification.get("visibility") == _public_visibility_from_member_auth(desired_auth),
545
+ "visibility_verified": _visibility_matches_expected(
546
+ verification.get("visibility"),
547
+ _public_visibility_from_member_auth(desired_auth),
548
+ ),
546
549
  },
547
550
  "verified": True,
548
551
  **{
@@ -8688,6 +8691,50 @@ def _visibility_summary(visibility: dict[str, Any]) -> dict[str, Any]:
8688
8691
  }
8689
8692
 
8690
8693
 
8694
+ def _visibility_matches_expected(actual: Any, expected: Any) -> bool:
8695
+ if not isinstance(actual, dict) or not isinstance(expected, dict):
8696
+ return False
8697
+ if str(actual.get("mode") or "") != str(expected.get("mode") or ""):
8698
+ return False
8699
+ if str(actual.get("external_mode") or "") != str(expected.get("external_mode") or ""):
8700
+ return False
8701
+
8702
+ actual_selectors = actual.get("selectors") if isinstance(actual.get("selectors"), dict) else {}
8703
+ expected_selectors = expected.get("selectors") if isinstance(expected.get("selectors"), dict) else {}
8704
+ actual_external = actual.get("external_selectors") if isinstance(actual.get("external_selectors"), dict) else {}
8705
+ expected_external = expected.get("external_selectors") if isinstance(expected.get("external_selectors"), dict) else {}
8706
+
8707
+ def sorted_values(source: dict[str, Any], key: str) -> list[str]:
8708
+ return sorted(str(item) for item in (source.get(key) or []) if item is not None and str(item).strip())
8709
+
8710
+ for key in ("member_uids", "dept_ids", "role_ids"):
8711
+ if sorted_values(actual_selectors, key) != sorted_values(expected_selectors, key):
8712
+ return False
8713
+ for key in ("member_ids", "dept_ids"):
8714
+ if sorted_values(actual_external, key) != sorted_values(expected_external, key):
8715
+ return False
8716
+
8717
+ # Backends commonly enrich id-based selectors with names/emails on readback; require
8718
+ # caller-specified text selectors only when no id selector anchors that subject.
8719
+ text_selector_pairs = (
8720
+ (actual_selectors, expected_selectors, "member_uids", "member_emails"),
8721
+ (actual_selectors, expected_selectors, "member_uids", "member_names"),
8722
+ (actual_selectors, expected_selectors, "dept_ids", "dept_names"),
8723
+ (actual_selectors, expected_selectors, "role_ids", "role_names"),
8724
+ (actual_external, expected_external, "member_ids", "member_emails"),
8725
+ )
8726
+ for actual_group, expected_group, id_key, text_key in text_selector_pairs:
8727
+ if sorted_values(expected_group, id_key):
8728
+ continue
8729
+ expected_text = sorted_values(expected_group, text_key)
8730
+ if expected_text and sorted_values(actual_group, text_key) != expected_text:
8731
+ return False
8732
+
8733
+ if "include_sub_departs" in expected_selectors and actual_selectors.get("include_sub_departs") != expected_selectors.get("include_sub_departs"):
8734
+ return False
8735
+ return True
8736
+
8737
+
8691
8738
  def _mapping_contains(actual: Any, expected: Any) -> bool:
8692
8739
  if isinstance(expected, dict):
8693
8740
  if not isinstance(actual, dict):