workspaces-euc-mcp-server 0.1.6__tar.gz → 0.1.7__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.6 → workspaces_euc_mcp_server-0.1.7}/CHANGELOG.md +10 -0
  2. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/DESIGN.md +1 -1
  3. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/PKG-INFO +2 -2
  4. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/README.md +1 -1
  5. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/iam/tier0-diagnostics.json +2 -1
  6. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/iam/tier1-cost.json +2 -1
  7. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/iam/tier2-lifecycle.json +2 -1
  8. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/iam/tier3-destructive.json +2 -1
  9. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/pyproject.toml +1 -1
  10. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_secure_browser.py +24 -8
  11. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/__init__.py +1 -1
  12. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/models.py +29 -0
  13. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/secure_browser.py +67 -16
  14. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.dockerignore +0 -0
  15. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.github/workflows/ci.yml +0 -0
  16. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.github/workflows/docker-publish.yml +0 -0
  17. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.github/workflows/publish.yml +0 -0
  18. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.gitignore +0 -0
  19. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/.pre-commit-config.yaml +0 -0
  20. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/Dockerfile +0 -0
  21. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/LICENSE +0 -0
  22. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/iam/README.md +0 -0
  23. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/scripts/smoke_readonly.py +0 -0
  24. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/__init__.py +0 -0
  25. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_clients.py +0 -0
  26. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_cost.py +0 -0
  27. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_destructive.py +0 -0
  28. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_diagnostics.py +0 -0
  29. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_governance.py +0 -0
  30. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_images.py +0 -0
  31. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_inventory.py +0 -0
  32. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_lifecycle.py +0 -0
  33. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_naming.py +0 -0
  34. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_no_embedded_secrets.py +0 -0
  35. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_performance.py +0 -0
  36. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_pricing.py +0 -0
  37. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/tests/test_reporting.py +0 -0
  38. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/clients.py +0 -0
  39. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/consts.py +0 -0
  40. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/server.py +0 -0
  41. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/__init__.py +0 -0
  42. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/_common.py +0 -0
  43. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/cost.py +0 -0
  44. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/destructive.py +0 -0
  45. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/diagnostics.py +0 -0
  46. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/governance.py +0 -0
  47. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/images.py +0 -0
  48. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/inventory.py +0 -0
  49. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/lifecycle.py +0 -0
  50. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/performance.py +0 -0
  51. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/pricing.py +0 -0
  52. {workspaces_euc_mcp_server-0.1.6 → workspaces_euc_mcp_server-0.1.7}/workspaces_euc_mcp_server/tools/reporting.py +0 -0
@@ -5,6 +5,16 @@ All notable changes to this project are documented here. The format is based on
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.7] - 2026-06-02
9
+
10
+ ### Fixed
11
+ - `get_secure_browser_portal_usage` now reports **current active sessions live via
12
+ `workspaces-web:ListSessions`** (the same source as the console's active-sessions view) instead
13
+ of inferring "active" from CloudWatch. CloudWatch (`AWS/WorkSpacesWeb`) is now used **only for
14
+ historic** metrics, and the summary clearly separates live vs historic. Adds
15
+ `workspaces-web:ListSessions` to every IAM tier. The tool now returns a `SecureBrowserPortalUsage`
16
+ result (`active_session_count`, `active_sessions`, `historic_metrics`).
17
+
8
18
  ## [0.1.6] - 2026-06-02
9
19
 
10
20
  ### Added
@@ -135,7 +135,7 @@ and return a synthesized result, not raw API passthroughs.
135
135
  | Tool | Purpose | IAM actions |
136
136
  |---|---|---|
137
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
- | `get_secure_browser_portal_usage` | Portal session/usage metrics | `workspaces-web:ListPortals`, `cloudwatch:GetMetricData` |
138
+ | `get_secure_browser_portal_usage` | Current active sessions (live, `ListSessions`) + historic CloudWatch metrics | `workspaces-web:ListPortals`, `workspaces-web:ListSessions`, `cloudwatch:GetMetricData` |
139
139
 
140
140
  **Reporting & audit**
141
141
  | Tool | Purpose | IAM actions |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workspaces-euc-mcp-server
3
- Version: 0.1.6
3
+ Version: 0.1.7
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
@@ -95,7 +95,7 @@ audit, and governance tools:
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
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
- | `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). |
98
+ | `get_secure_browser_portal_usage` | A Secure Browser portal's **current active sessions** (live, via `ListSessions` — same as the console) plus **historic** `AWS/WorkSpacesWeb` metrics over a window (CloudWatch is historic-only; idle portals publish none) (Tier 0). |
99
99
  | `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
100
100
 
101
101
  Cost/utilization tools need the **Tier 1** IAM policy ([`iam/tier1-cost.json`](iam/tier1-cost.json));
@@ -65,7 +65,7 @@ audit, and governance tools:
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
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
- | `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). |
68
+ | `get_secure_browser_portal_usage` | A Secure Browser portal's **current active sessions** (live, via `ListSessions` — same as the console) plus **historic** `AWS/WorkSpacesWeb` metrics over a window (CloudWatch is historic-only; idle portals publish none) (Tier 0). |
69
69
  | `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
70
70
 
71
71
  Cost/utilization tools need the **Tier 1** IAM policy ([`iam/tier1-cost.json`](iam/tier1-cost.json));
@@ -40,7 +40,8 @@
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
42
  "workspaces-web:GetNetworkSettings",
43
- "workspaces-web:GetDataProtectionSettings"
43
+ "workspaces-web:GetDataProtectionSettings",
44
+ "workspaces-web:ListSessions"
44
45
  ],
45
46
  "Resource": "*"
46
47
  },
@@ -40,7 +40,8 @@
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
42
  "workspaces-web:GetNetworkSettings",
43
- "workspaces-web:GetDataProtectionSettings"
43
+ "workspaces-web:GetDataProtectionSettings",
44
+ "workspaces-web:ListSessions"
44
45
  ],
45
46
  "Resource": "*"
46
47
  },
@@ -40,7 +40,8 @@
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
42
  "workspaces-web:GetNetworkSettings",
43
- "workspaces-web:GetDataProtectionSettings"
43
+ "workspaces-web:GetDataProtectionSettings",
44
+ "workspaces-web:ListSessions"
44
45
  ],
45
46
  "Resource": "*"
46
47
  },
@@ -40,7 +40,8 @@
40
40
  "workspaces-web:ListNetworkSettings",
41
41
  "workspaces-web:GetUserSettings",
42
42
  "workspaces-web:GetNetworkSettings",
43
- "workspaces-web:GetDataProtectionSettings"
43
+ "workspaces-web:GetDataProtectionSettings",
44
+ "workspaces-web:ListSessions"
44
45
  ],
45
46
  "Resource": "*"
46
47
  },
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workspaces-euc-mcp-server"
3
- version = "0.1.6"
3
+ version = "0.1.7"
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"
@@ -108,19 +108,35 @@ def test_portal_details_resolves_data_protection_config():
108
108
  assert dp["global_enforced_urls"] == ["*"]
109
109
 
110
110
 
111
- def test_portal_usage_empty_explains_session_model():
112
- cw = types.SimpleNamespace(
113
- get_metric_data=lambda **_: {"MetricDataResults": []} # no data
114
- )
115
- factory = FakeFactory({consts.CLOUDWATCH_API: cw})
111
+ def test_portal_usage_returns_live_active_sessions_and_historic():
112
+ # Active sessions must come LIVE from ListSessions (status=Active), not from CloudWatch.
113
+ captured = {}
114
+
115
+ def list_sessions(**kwargs):
116
+ captured.update(kwargs)
117
+ return {
118
+ "sessions": [
119
+ {"sessionId": "s-1", "username": "alice", "status": "Active"},
120
+ {"sessionId": "s-2", "username": "bob", "status": "Active"},
121
+ ]
122
+ }
123
+
124
+ web = types.SimpleNamespace(list_sessions=list_sessions)
125
+ cw = types.SimpleNamespace(get_metric_data=lambda **_: {"MetricDataResults": []}) # no historic
126
+ factory = FakeFactory({consts.SECURE_BROWSER_API: web, consts.CLOUDWATCH_API: cw})
116
127
 
117
128
  usage = secure_browser.get_secure_browser_portal_usage_core(
118
129
  factory, "arn:aws:workspaces-web:r:a:portal/abc123", "us-east-1"
119
130
  )
120
131
 
121
- assert usage.target_type == consts.PRODUCT_SECURE_BROWSER
122
- assert usage.target_id.endswith("portal/abc123")
123
- assert "Session Logger" in (usage.summary or "")
132
+ # Queried ListSessions for the portal id with status=Active.
133
+ assert captured["portalId"] == "abc123"
134
+ assert captured["status"] == "Active"
135
+ # Live active sessions populated; CloudWatch reserved for (empty) historic.
136
+ assert usage.active_session_count == 2
137
+ assert {s.username for s in usage.active_sessions} == {"alice", "bob"}
138
+ assert usage.historic_metrics == {}
139
+ assert "active session(s) right now" in (usage.summary or "")
124
140
 
125
141
 
126
142
  def test_portal_id_extracted_from_arn():
@@ -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.6"
6
+ __version__ = "0.1.7"
@@ -166,6 +166,35 @@ class SecureBrowserPortalDetails(BaseModel):
166
166
  errors: list[ServiceError] = Field(default_factory=list)
167
167
 
168
168
 
169
+ class SecureBrowserSession(BaseModel):
170
+ """A single Secure Browser portal session (from ListSessions)."""
171
+
172
+ session_id: str
173
+ username: str | None = None
174
+ status: str | None = None
175
+ start_time: str | None = None
176
+
177
+
178
+ class SecureBrowserPortalUsage(BaseModel):
179
+ """Live active sessions (ListSessions) plus historic metrics (CloudWatch) for a portal."""
180
+
181
+ portal: str
182
+ active_session_count: int = 0
183
+ active_sessions: list[SecureBrowserSession] = Field(
184
+ default_factory=list,
185
+ description="Sessions currently Active, retrieved live from ListSessions (like the "
186
+ "console).",
187
+ )
188
+ lookback_days: int
189
+ period_hours: int
190
+ historic_metrics: dict[str, FleetMetricSeries] = Field(
191
+ default_factory=dict,
192
+ description="Historic session metrics from CloudWatch (AWS/WorkSpacesWeb) over the window.",
193
+ )
194
+ summary: str | None = None
195
+ errors: list[ServiceError] = Field(default_factory=list)
196
+
197
+
169
198
  class UsageHistory(BaseModel):
170
199
  """Generic metric time-series history for a single EUC resource over a window."""
171
200
 
@@ -18,8 +18,13 @@ from typing import Any
18
18
 
19
19
  from .. import consts
20
20
  from ..clients import ClientFactory
21
- from ..models import SecureBrowserPortalDetails, ServiceError, UsageHistory
22
- from ._common import read_only, try_call
21
+ from ..models import (
22
+ SecureBrowserPortalDetails,
23
+ SecureBrowserPortalUsage,
24
+ SecureBrowserSession,
25
+ ServiceError,
26
+ )
27
+ from ._common import paginate, read_only, try_call
23
28
  from .performance import _fetch_metric_series
24
29
 
25
30
 
@@ -139,14 +144,54 @@ def _portal_id(portal: str) -> str:
139
144
  return portal.rsplit("/", 1)[-1] if "/" in portal else portal
140
145
 
141
146
 
147
+ def _list_active_sessions(
148
+ web: Any, portal_id: str, errors: list[ServiceError]
149
+ ) -> list[SecureBrowserSession]:
150
+ """Current Active sessions for a portal, live from ListSessions (what the console shows)."""
151
+ raw = try_call(
152
+ errors,
153
+ consts.PRODUCT_SECURE_BROWSER,
154
+ "ListSessions",
155
+ lambda: paginate(
156
+ web.list_sessions,
157
+ "sessions",
158
+ pagination_in="nextToken",
159
+ pagination_out="nextToken",
160
+ portalId=portal_id,
161
+ status="Active",
162
+ ),
163
+ default=[],
164
+ )
165
+ sessions: list[SecureBrowserSession] = []
166
+ for s in raw or []:
167
+ start = s.get("startTime")
168
+ sessions.append(
169
+ SecureBrowserSession(
170
+ session_id=s.get("sessionId", ""),
171
+ username=s.get("username"),
172
+ status=s.get("status"),
173
+ start_time=start.isoformat() if hasattr(start, "isoformat") else None,
174
+ )
175
+ )
176
+ return sessions
177
+
178
+
142
179
  def get_secure_browser_portal_usage_core(
143
180
  factory: ClientFactory,
144
181
  portal: str,
145
182
  region: str | None,
146
183
  lookback_days: int = 7,
147
184
  period_hours: int = 24,
148
- ) -> UsageHistory:
185
+ ) -> SecureBrowserPortalUsage:
149
186
  errors: list[ServiceError] = []
187
+ portal_id = _portal_id(portal)
188
+ web = factory.client(consts.SECURE_BROWSER_API, region=region)
189
+
190
+ # LIVE: current active sessions come from ListSessions (the real-time source the console uses),
191
+ # NOT from CloudWatch.
192
+ active = _list_active_sessions(web, portal_id, errors)
193
+
194
+ # HISTORIC: CloudWatch (AWS/WorkSpacesWeb) over the window — only meaningful for past activity.
150
195
  cloudwatch = factory.client(consts.CLOUDWATCH_API, region=region)
151
196
  metrics = try_call(
152
197
  errors,
@@ -156,26 +201,30 @@ def get_secure_browser_portal_usage_core(
156
201
  cloudwatch,
157
202
  consts.SECURE_BROWSER_NAMESPACE,
158
203
  consts.SECURE_BROWSER_PORTAL_DIMENSION,
159
- _portal_id(portal),
204
+ portal_id,
160
205
  consts.SECURE_BROWSER_SESSION_METRICS,
161
206
  lookback_days,
162
207
  period_hours,
163
208
  ),
164
209
  default={},
165
210
  )
211
+
212
+ hist = (
213
+ f"historic metrics over {lookback_days}d available (see historic_metrics)"
214
+ if metrics
215
+ else f"no historic session metrics in the last {lookback_days}d (idle portals publish "
216
+ "none to CloudWatch; enable the portal's Session Logger for detail)"
217
+ )
166
218
  summary = (
167
- "No session metrics in the window. Secure Browser only emits CloudWatch metrics when "
168
- "sessions occur (idle portals publish nothing); for detailed usage enable the portal's "
169
- "Session Logger."
170
- if not metrics
171
- else f"Session metrics over {lookback_days}d: see series."
219
+ f"{len(active)} active session(s) right now (live via ListSessions). Historic: {hist}."
172
220
  )
173
- return UsageHistory(
174
- target_type=consts.PRODUCT_SECURE_BROWSER,
175
- target_id=portal,
221
+ return SecureBrowserPortalUsage(
222
+ portal=portal,
223
+ active_session_count=len(active),
224
+ active_sessions=active,
176
225
  lookback_days=lookback_days,
177
226
  period_hours=period_hours,
178
- metrics=metrics or {},
227
+ historic_metrics=metrics or {},
179
228
  summary=summary,
180
229
  errors=errors,
181
230
  )
@@ -210,10 +259,12 @@ def register(mcp: Any, factory: ClientFactory) -> None:
210
259
  lookback_days: int = 7,
211
260
  period_hours: int = 24,
212
261
  ) -> dict[str, Any]:
213
- """Get a Secure Browser portal's session metrics (AWS/WorkSpacesWeb) over a window.
262
+ """Get a Secure Browser portal's CURRENT active sessions plus historic session metrics.
214
263
 
215
- NOTE: Secure Browser only emits these metrics when sessions occur, so idle portals return
216
- nothing; richer per-session data is available via the portal's Session Logger. Read-only.
264
+ Active sessions are retrieved **live** from ListSessions (the same source as the console's
265
+ active-sessions view). Historic usage comes from CloudWatch (AWS/WorkSpacesWeb) over the
266
+ window — CloudWatch is only meaningful for *past* activity, and idle portals publish none.
267
+ Read-only.
217
268
 
218
269
  Args:
219
270
  portal: The portal id or ARN.