workspaces-euc-mcp-server 0.1.5__tar.gz → 0.1.6__tar.gz

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.
Files changed (52) hide show
  1. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/CHANGELOG.md +12 -0
  2. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/DESIGN.md +2 -2
  3. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/PKG-INFO +3 -3
  4. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/README.md +2 -2
  5. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier0-diagnostics.json +2 -1
  6. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier1-cost.json +2 -1
  7. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier2-lifecycle.json +2 -1
  8. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier3-destructive.json +2 -1
  9. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/pyproject.toml +1 -1
  10. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_diagnostics.py +39 -0
  11. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_secure_browser.py +43 -0
  12. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/__init__.py +1 -1
  13. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/models.py +7 -0
  14. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/diagnostics.py +14 -1
  15. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/secure_browser.py +47 -2
  16. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.dockerignore +0 -0
  17. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/ci.yml +0 -0
  18. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/docker-publish.yml +0 -0
  19. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/publish.yml +0 -0
  20. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.gitignore +0 -0
  21. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.pre-commit-config.yaml +0 -0
  22. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/Dockerfile +0 -0
  23. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/LICENSE +0 -0
  24. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/README.md +0 -0
  25. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/scripts/smoke_readonly.py +0 -0
  26. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/__init__.py +0 -0
  27. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_clients.py +0 -0
  28. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_cost.py +0 -0
  29. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_destructive.py +0 -0
  30. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_governance.py +0 -0
  31. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_images.py +0 -0
  32. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_inventory.py +0 -0
  33. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_lifecycle.py +0 -0
  34. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_naming.py +0 -0
  35. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_no_embedded_secrets.py +0 -0
  36. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_performance.py +0 -0
  37. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_pricing.py +0 -0
  38. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_reporting.py +0 -0
  39. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/clients.py +0 -0
  40. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/consts.py +0 -0
  41. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/server.py +0 -0
  42. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/__init__.py +0 -0
  43. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/_common.py +0 -0
  44. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/cost.py +0 -0
  45. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/destructive.py +0 -0
  46. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/governance.py +0 -0
  47. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/images.py +0 -0
  48. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/inventory.py +0 -0
  49. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/lifecycle.py +0 -0
  50. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/performance.py +0 -0
  51. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/pricing.py +0 -0
  52. {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/reporting.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project are documented here. The format is based on
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.6] - 2026-06-02
9
+
10
+ ### Added
11
+ - `check_directory_health` now surfaces each directory's **registration properties** in its
12
+ signals — notably the target **OU** (`WorkspaceCreationProperties.DefaultOu`), plus custom
13
+ security group, local-admin / internet-access / maintenance-mode flags, and directory/workspace
14
+ type. (AD-backed directories carry an OU; WorkSpaces-managed ones return none.)
15
+ - `get_secure_browser_portal_details` now resolves the portal's **data-protection configuration**
16
+ when attached: the redacted built-in/custom inline-redaction patterns, global confidence level,
17
+ and enforced/exempt URLs — not just whether data protection is on. Adds
18
+ `workspaces-web:GetDataProtectionSettings` to every IAM tier.
19
+
8
20
  ## [0.1.5] - 2026-06-02
9
21
 
10
22
  ### Added
@@ -117,7 +117,7 @@ and return a synthesized result, not raw API passthroughs.
117
117
  | `diagnose_workspace_connectivity` | Correlate a Personal WorkSpace's state + connection status + directory health + CloudWatch into a root-cause narrative | `workspaces:Describe*`, `ds:DescribeDirectories`, `cloudwatch:GetMetricData` |
118
118
  | `diagnose_pool` | Why a Pool is unhealthy/queued — capacity status, sessions, errors, scaling | `workspaces:DescribeWorkspacesPool*`, `cloudwatch:GetMetricData` |
119
119
  | `diagnose_application_fleet` | Fleet state, capacity, scaling activity, fleet errors | `appstream:DescribeFleets`, `appstream:ListAssociatedStacks`, `cloudwatch:GetMetricData`, `application-autoscaling:DescribeScalingActivities` |
120
- | `check_directory_health` | Shared dependency: directory reachability/registration (skips `ds` for WorkSpaces-managed `wsd-` directories) | `ds:DescribeDirectories`, `workspaces:DescribeWorkspaceDirectories` |
120
+ | `check_directory_health` | Shared dependency: directory reachability/registration + registration properties (target OU, security group, flags); skips `ds` for WorkSpaces-managed `wsd-` directories | `ds:DescribeDirectories`, `workspaces:DescribeWorkspaceDirectories` |
121
121
 
122
122
  **Cost, utilization & performance**
123
123
  | Tool | Purpose | IAM actions |
@@ -134,7 +134,7 @@ and return a synthesized result, not raw API passthroughs.
134
134
  **Secure Browser**
135
135
  | Tool | Purpose | IAM actions |
136
136
  |---|---|---|
137
- | `get_secure_browser_portal_details` | Portal config + associated settings (browser/network/user/IP-access) | `workspaces-web:GetPortal`, `workspaces-web:List*`, `workspaces-web:Get*Settings` |
137
+ | `get_secure_browser_portal_details` | Portal config + associated settings (browser/network/user/IP-access) + resolved data-protection redaction config | `workspaces-web:GetPortal`, `workspaces-web:List*`, `workspaces-web:Get*Settings`, `workspaces-web:GetDataProtectionSettings` |
138
138
  | `get_secure_browser_portal_usage` | Portal session/usage metrics | `workspaces-web:ListPortals`, `cloudwatch:GetMetricData` |
139
139
 
140
140
  **Reporting & audit**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workspaces-euc-mcp-server
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core).
5
5
  Project-URL: Homepage, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
6
6
  Project-URL: Repository, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
@@ -81,7 +81,7 @@ audit, and governance tools:
81
81
  | `diagnose_application_fleet` | A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
82
82
  | `diagnose_pool` | A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
83
83
  | `get_application_fleet_usage` | A WorkSpaces Applications fleet's **usage history** — AWS/AppStream capacity/utilization time-series over a window, with a plain-language summary (e.g. idle running capacity) (Tier 0). |
84
- | `check_directory_health` | Registration state and AWS Directory Service stage for one or all WorkSpaces-registered directories. |
84
+ | `check_directory_health` | Registration state, AWS Directory Service stage, and **registration properties** (target **OU**, custom security group, local-admin / internet-access / maintenance-mode flags) for one or all WorkSpaces-registered directories. |
85
85
  | `analyze_workspace_utilization` | Classifies WorkSpaces Personal desktops as unused / idle / active from the `UserConnected` metric (Tier 1). |
86
86
  | `recommend_running_mode` | Flags AlwaysOn desktops with low usage as AutoStop candidates, with an **estimated $/mo saving** where the bundle price can be matched (Tier 1). |
87
87
  | `get_workspace_performance` | Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from `AWS/WorkSpaces` — no CloudWatch agent (Tier 0). |
@@ -94,7 +94,7 @@ audit, and governance tools:
94
94
  | `audit_application_images` | Audits WorkSpaces Applications (AppStream 2.0) **images and image builders** — flags stale base images (unpatched OS), pinned/old AppStream agents, non-AVAILABLE/errored images, SHARED cross-account visibility, and **image builders left RUNNING** (cost + admin surface) (Tier 0). |
95
95
  | `get_euc_audit_trail` | **"Who changed what"** — recent EUC management events from CloudTrail (last 90 days, no trail required) across all services; mutations-only by default, flags destructive actions and errors (e.g. AccessDenied) (Tier 0). |
96
96
  | `get_euc_service_quotas` | **Service-quota limits + usage headroom** per EUC service; pairs limits with current usage (where AWS publishes a usage metric) to flag quotas approaching their limit — capacity planning (Tier 0). |
97
- | `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
97
+ | `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, attached policies, and — when configured — the **data-protection redaction config** (which built-in/custom patterns are redacted, confidence level, enforced/exempt URLs) (Tier 0). |
98
98
  | `get_secure_browser_portal_usage` | A Secure Browser portal's `AWS/WorkSpacesWeb` session metrics over a window (empty until the portal has sessions; Session Logger gives detail) (Tier 0). |
99
99
  | `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
100
100
 
@@ -51,7 +51,7 @@ audit, and governance tools:
51
51
  | `diagnose_application_fleet` | A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
52
52
  | `diagnose_pool` | A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
53
53
  | `get_application_fleet_usage` | A WorkSpaces Applications fleet's **usage history** — AWS/AppStream capacity/utilization time-series over a window, with a plain-language summary (e.g. idle running capacity) (Tier 0). |
54
- | `check_directory_health` | Registration state and AWS Directory Service stage for one or all WorkSpaces-registered directories. |
54
+ | `check_directory_health` | Registration state, AWS Directory Service stage, and **registration properties** (target **OU**, custom security group, local-admin / internet-access / maintenance-mode flags) for one or all WorkSpaces-registered directories. |
55
55
  | `analyze_workspace_utilization` | Classifies WorkSpaces Personal desktops as unused / idle / active from the `UserConnected` metric (Tier 1). |
56
56
  | `recommend_running_mode` | Flags AlwaysOn desktops with low usage as AutoStop candidates, with an **estimated $/mo saving** where the bundle price can be matched (Tier 1). |
57
57
  | `get_workspace_performance` | Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from `AWS/WorkSpaces` — no CloudWatch agent (Tier 0). |
@@ -64,7 +64,7 @@ audit, and governance tools:
64
64
  | `audit_application_images` | Audits WorkSpaces Applications (AppStream 2.0) **images and image builders** — flags stale base images (unpatched OS), pinned/old AppStream agents, non-AVAILABLE/errored images, SHARED cross-account visibility, and **image builders left RUNNING** (cost + admin surface) (Tier 0). |
65
65
  | `get_euc_audit_trail` | **"Who changed what"** — recent EUC management events from CloudTrail (last 90 days, no trail required) across all services; mutations-only by default, flags destructive actions and errors (e.g. AccessDenied) (Tier 0). |
66
66
  | `get_euc_service_quotas` | **Service-quota limits + usage headroom** per EUC service; pairs limits with current usage (where AWS publishes a usage metric) to flag quotas approaching their limit — capacity planning (Tier 0). |
67
- | `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
67
+ | `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, attached policies, and — when configured — the **data-protection redaction config** (which built-in/custom patterns are redacted, confidence level, enforced/exempt URLs) (Tier 0). |
68
68
  | `get_secure_browser_portal_usage` | A Secure Browser portal's `AWS/WorkSpacesWeb` session metrics over a window (empty until the portal has sessions; Session Logger gives detail) (Tier 0). |
69
69
  | `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
70
70
 
@@ -39,7 +39,8 @@
39
39
  "workspaces-web:ListUserSettings",
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
- "workspaces-web:GetNetworkSettings"
42
+ "workspaces-web:GetNetworkSettings",
43
+ "workspaces-web:GetDataProtectionSettings"
43
44
  ],
44
45
  "Resource": "*"
45
46
  },
@@ -39,7 +39,8 @@
39
39
  "workspaces-web:ListUserSettings",
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
- "workspaces-web:GetNetworkSettings"
42
+ "workspaces-web:GetNetworkSettings",
43
+ "workspaces-web:GetDataProtectionSettings"
43
44
  ],
44
45
  "Resource": "*"
45
46
  },
@@ -39,7 +39,8 @@
39
39
  "workspaces-web:ListUserSettings",
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
- "workspaces-web:GetNetworkSettings"
42
+ "workspaces-web:GetNetworkSettings",
43
+ "workspaces-web:GetDataProtectionSettings"
43
44
  ],
44
45
  "Resource": "*"
45
46
  },
@@ -39,7 +39,8 @@
39
39
  "workspaces-web:ListUserSettings",
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
- "workspaces-web:GetNetworkSettings"
42
+ "workspaces-web:GetNetworkSettings",
43
+ "workspaces-web:GetDataProtectionSettings"
43
44
  ],
44
45
  "Resource": "*"
45
46
  },
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workspaces-euc-mcp-server"
3
- version = "0.1.5"
3
+ version = "0.1.6"
4
4
  description = "MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -161,6 +161,45 @@ def test_directory_health_skips_ds_for_workspaces_managed_directory():
161
161
  assert any("WorkSpaces-managed" in f.title for f in d.findings)
162
162
 
163
163
 
164
+ def test_directory_health_surfaces_registration_ou_and_properties():
165
+ # The registration OU (WorkspaceCreationProperties.DefaultOu) and related properties must be
166
+ # exposed in the diagnosis signals.
167
+ workspaces = types.SimpleNamespace(
168
+ describe_workspace_directories=lambda **_: {
169
+ "Directories": [
170
+ {
171
+ "DirectoryId": "d-0123456789",
172
+ "State": "REGISTERED",
173
+ "DirectoryType": "AD_CONNECTOR",
174
+ "WorkspaceType": "PERSONAL",
175
+ "WorkspaceCreationProperties": {
176
+ "DefaultOu": "OU=AmazonWorkspaces,OU=Singapore,DC=bg,DC=local",
177
+ "CustomSecurityGroupId": "sg-0abc",
178
+ "UserEnabledAsLocalAdministrator": True,
179
+ "EnableInternetAccess": False,
180
+ "EnableMaintenanceMode": True,
181
+ },
182
+ }
183
+ ]
184
+ },
185
+ )
186
+ ds = types.SimpleNamespace(
187
+ describe_directories=lambda **_: {
188
+ "DirectoryDescriptions": [{"DirectoryId": "d-0123456789", "Stage": "Active"}]
189
+ },
190
+ )
191
+ factory = FakeFactory({consts.WORKSPACES_API: workspaces, consts.DIRECTORY_API: ds})
192
+
193
+ report = diagnostics.check_directory_health_core(factory, "d-0123456789", "us-east-1")
194
+
195
+ sig = report.directories[0].signals
196
+ assert sig["default_ou"] == "OU=AmazonWorkspaces,OU=Singapore,DC=bg,DC=local"
197
+ assert sig["directory_type"] == "AD_CONNECTOR"
198
+ assert sig["custom_security_group_id"] == "sg-0abc"
199
+ assert sig["user_enabled_as_local_administrator"] is True
200
+ assert sig["enable_maintenance_mode"] is True
201
+
202
+
164
203
  def test_directory_health_flags_impaired_stage():
165
204
  workspaces = types.SimpleNamespace(
166
205
  describe_workspace_directories=lambda **_: {
@@ -65,6 +65,49 @@ def test_portal_details_resolves_settings():
65
65
  assert d.has_data_protection is False
66
66
 
67
67
 
68
+ def test_portal_details_resolves_data_protection_config():
69
+ web = types.SimpleNamespace(
70
+ get_portal=lambda **_: {
71
+ "portal": {
72
+ "portalArn": "arn:portal/2",
73
+ "displayName": "Okta",
74
+ "dataProtectionSettingsArn": "arn:dp/1",
75
+ }
76
+ },
77
+ get_data_protection_settings=lambda **_: {
78
+ "dataProtectionSettings": {
79
+ "displayName": "ip-address-dp",
80
+ "inlineRedactionConfiguration": {
81
+ "inlineRedactionPatterns": [
82
+ {"builtInPatternId": "ipAddr"},
83
+ {"builtInPatternId": "macAddr"},
84
+ {
85
+ "customPattern": {
86
+ "patternName": "EmployeeId",
87
+ "keywordRegex": "EMP-\\d+",
88
+ }
89
+ },
90
+ ],
91
+ "globalEnforcedUrls": ["*"],
92
+ "globalConfidenceLevel": 2,
93
+ },
94
+ }
95
+ },
96
+ )
97
+ factory = FakeFactory({consts.SECURE_BROWSER_API: web})
98
+
99
+ d = secure_browser.get_secure_browser_portal_details_core(factory, "arn:portal/2", "us-east-1")
100
+
101
+ assert d.has_data_protection is True
102
+ dp = d.data_protection
103
+ assert dp["display_name"] == "ip-address-dp"
104
+ assert dp["redacted_pattern_count"] == 3
105
+ assert dp["builtin_patterns"] == ["ipAddr", "macAddr"]
106
+ assert dp["custom_patterns"][0]["name"] == "EmployeeId"
107
+ assert dp["global_confidence_level"] == 2
108
+ assert dp["global_enforced_urls"] == ["*"]
109
+
110
+
68
111
  def test_portal_usage_empty_explains_session_model():
69
112
  cw = types.SimpleNamespace(
70
113
  get_metric_data=lambda **_: {"MetricDataResults": []} # no data
@@ -3,4 +3,4 @@
3
3
  # A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0
4
4
  """MCP server for administering the Amazon WorkSpaces End User Computing portfolio."""
5
5
 
6
- __version__ = "0.1.5"
6
+ __version__ = "0.1.6"
@@ -156,6 +156,13 @@ class SecureBrowserPortalDetails(BaseModel):
156
156
  network: dict[str, object] = Field(default_factory=dict)
157
157
  has_browser_policy: bool = False
158
158
  has_data_protection: bool = False
159
+ data_protection: dict[str, object] = Field(
160
+ default_factory=dict,
161
+ description=(
162
+ "Resolved inline-redaction configuration when data protection is attached: redacted "
163
+ "patterns (built-in + custom), global confidence level, and enforced/exempt URLs."
164
+ ),
165
+ )
159
166
  errors: list[ServiceError] = Field(default_factory=list)
160
167
 
161
168
 
@@ -279,9 +279,22 @@ def _diagnose_directory_into(
279
279
  )
280
280
  dirs = (reg or {}).get("Directories", [])
281
281
  if dirs:
282
- reg_state = dirs[0].get("State", "UNKNOWN")
282
+ d0 = dirs[0]
283
+ reg_state = d0.get("State", "UNKNOWN")
283
284
  if signals is not None:
284
285
  signals[f"{signals_prefix}registration_state"] = reg_state
286
+ signals[f"{signals_prefix}directory_type"] = d0.get("DirectoryType")
287
+ signals[f"{signals_prefix}workspace_type"] = d0.get("WorkspaceType")
288
+ # Registration-level WorkspaceCreationProperties — notably the target OU. Only AD-backed
289
+ # directories carry a DefaultOu; WorkSpaces-managed (Entra/internal) ones return None.
290
+ wcp = d0.get("WorkspaceCreationProperties") or {}
291
+ signals[f"{signals_prefix}default_ou"] = wcp.get("DefaultOu")
292
+ signals[f"{signals_prefix}custom_security_group_id"] = wcp.get("CustomSecurityGroupId")
293
+ signals[f"{signals_prefix}user_enabled_as_local_administrator"] = wcp.get(
294
+ "UserEnabledAsLocalAdministrator"
295
+ )
296
+ signals[f"{signals_prefix}enable_internet_access"] = wcp.get("EnableInternetAccess")
297
+ signals[f"{signals_prefix}enable_maintenance_mode"] = wcp.get("EnableMaintenanceMode")
285
298
  if reg_state != "REGISTERED":
286
299
  findings.append(
287
300
  Finding(
@@ -78,6 +78,19 @@ def get_secure_browser_portal_details_core(
78
78
  if key in (ns or {}):
79
79
  network[key] = ns[key]
80
80
 
81
+ data_protection: dict[str, object] = {}
82
+ if portal.get("dataProtectionSettingsArn"):
83
+ dps = try_call(
84
+ errors,
85
+ consts.PRODUCT_SECURE_BROWSER,
86
+ "GetDataProtectionSettings",
87
+ lambda: web.get_data_protection_settings(
88
+ dataProtectionSettingsArn=portal["dataProtectionSettingsArn"]
89
+ ).get("dataProtectionSettings", {}),
90
+ default={},
91
+ )
92
+ data_protection = _summarize_data_protection(dps or {})
93
+
81
94
  return SecureBrowserPortalDetails(
82
95
  portal_arn=portal_arn,
83
96
  display_name=portal.get("displayName"),
@@ -87,10 +100,40 @@ def get_secure_browser_portal_details_core(
87
100
  network=network,
88
101
  has_browser_policy=bool(portal.get("browserSettingsArn")),
89
102
  has_data_protection=bool(portal.get("dataProtectionSettingsArn")),
103
+ data_protection=data_protection,
90
104
  errors=errors,
91
105
  )
92
106
 
93
107
 
108
+ def _summarize_data_protection(dps: dict[str, Any]) -> dict[str, Any]:
109
+ """Reduce a data-protection settings object to the policy-relevant redaction configuration."""
110
+ inline: dict[str, Any] = dps.get("inlineRedactionConfiguration") or {}
111
+ patterns: list[dict[str, Any]] = inline.get("inlineRedactionPatterns") or []
112
+ builtin: list[str] = []
113
+ custom: list[dict[str, Any]] = []
114
+ for p in patterns:
115
+ if p.get("builtInPatternId"):
116
+ builtin.append(p["builtInPatternId"])
117
+ elif p.get("customPattern"):
118
+ cp = p["customPattern"]
119
+ custom.append(
120
+ {
121
+ "name": cp.get("patternName"),
122
+ "description": cp.get("patternDescription"),
123
+ "keyword_regex": cp.get("keywordRegex"),
124
+ }
125
+ )
126
+ return {
127
+ "display_name": dps.get("displayName"),
128
+ "redacted_pattern_count": len(patterns),
129
+ "builtin_patterns": builtin,
130
+ "custom_patterns": custom,
131
+ "global_confidence_level": inline.get("globalConfidenceLevel"),
132
+ "global_enforced_urls": inline.get("globalEnforcedUrls"),
133
+ "global_exempt_urls": inline.get("globalExemptUrls"),
134
+ }
135
+
136
+
94
137
  def _portal_id(portal: str) -> str:
95
138
  """Accept a portal ARN or id; the CloudWatch dimension uses the id (last ARN segment)."""
96
139
  return portal.rsplit("/", 1)[-1] if "/" in portal else portal
@@ -147,8 +190,10 @@ def register(mcp: Any, factory: ClientFactory) -> None:
147
190
  """Resolve a WorkSpaces Secure Browser portal's settings (security-relevant).
148
191
 
149
192
  Returns the portal's user settings (clipboard copy/paste, file download/upload, print
150
- controls + timeouts), network (VPC/subnets/security groups), and whether a browser policy
151
- and data-protection settings are attached. Read-only.
193
+ controls + timeouts), network (VPC/subnets/security groups), whether a browser policy is
194
+ attached, and — when data protection is configured the resolved inline-redaction config
195
+ (which built-in/custom patterns are redacted, global confidence, enforced/exempt URLs).
196
+ Read-only.
152
197
 
153
198
  Args:
154
199
  portal_arn: The portal ARN (from get_euc_inventory_summary / generate_inventory_report).