workspaces-euc-mcp-server 0.1.1__py3-none-any.whl
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.
- workspaces_euc_mcp_server/__init__.py +6 -0
- workspaces_euc_mcp_server/clients.py +101 -0
- workspaces_euc_mcp_server/consts.py +154 -0
- workspaces_euc_mcp_server/models.py +333 -0
- workspaces_euc_mcp_server/server.py +129 -0
- workspaces_euc_mcp_server/tools/__init__.py +4 -0
- workspaces_euc_mcp_server/tools/_common.py +87 -0
- workspaces_euc_mcp_server/tools/cost.py +314 -0
- workspaces_euc_mcp_server/tools/destructive.py +307 -0
- workspaces_euc_mcp_server/tools/diagnostics.py +799 -0
- workspaces_euc_mcp_server/tools/inventory.py +158 -0
- workspaces_euc_mcp_server/tools/lifecycle.py +564 -0
- workspaces_euc_mcp_server/tools/performance.py +620 -0
- workspaces_euc_mcp_server/tools/pricing.py +152 -0
- workspaces_euc_mcp_server/tools/reporting.py +529 -0
- workspaces_euc_mcp_server/tools/secure_browser.py +190 -0
- workspaces_euc_mcp_server-0.1.1.dist-info/METADATA +270 -0
- workspaces_euc_mcp_server-0.1.1.dist-info/RECORD +21 -0
- workspaces_euc_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- workspaces_euc_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
- workspaces_euc_mcp_server-0.1.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Copyright bengroeneveldsg. Licensed under the Apache License, Version 2.0 (the "License").
|
|
2
|
+
# You may not use this file except in compliance with the License.
|
|
3
|
+
# A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
"""WorkSpaces Secure Browser (formerly WorkSpaces Web) tools (read-only, IAM Tier 0).
|
|
5
|
+
|
|
6
|
+
Brings Secure Browser to parity with the other services:
|
|
7
|
+
- ``get_secure_browser_portal_details`` resolves a portal's user/browser/network/data-protection
|
|
8
|
+
settings — the clipboard/print/download data-egress controls that matter for security. Available
|
|
9
|
+
and validatable now.
|
|
10
|
+
- ``get_secure_browser_portal_usage`` returns session metrics from AWS/WorkSpacesWeb. Unlike the
|
|
11
|
+
WorkSpaces capacity metrics, Secure Browser only emits these when sessions occur, so it is empty
|
|
12
|
+
for idle portals; the metric names follow AWS docs and are best-effort until there is activity.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from .. import consts
|
|
20
|
+
from ..clients import ClientFactory
|
|
21
|
+
from ..models import SecureBrowserPortalDetails, ServiceError, UsageHistory
|
|
22
|
+
from ._common import read_only, try_call
|
|
23
|
+
from .performance import _fetch_metric_series
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_secure_browser_portal_details_core(
|
|
27
|
+
factory: ClientFactory, portal_arn: str, region: str | None
|
|
28
|
+
) -> SecureBrowserPortalDetails:
|
|
29
|
+
errors: list[ServiceError] = []
|
|
30
|
+
web = factory.client(consts.SECURE_BROWSER_API, region=region)
|
|
31
|
+
|
|
32
|
+
portal = try_call(
|
|
33
|
+
errors,
|
|
34
|
+
consts.PRODUCT_SECURE_BROWSER,
|
|
35
|
+
"GetPortal",
|
|
36
|
+
lambda: web.get_portal(portalArn=portal_arn).get("portal", {}),
|
|
37
|
+
default={},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
user_settings: dict[str, object] = {}
|
|
41
|
+
if portal.get("userSettingsArn"):
|
|
42
|
+
us = try_call(
|
|
43
|
+
errors,
|
|
44
|
+
consts.PRODUCT_SECURE_BROWSER,
|
|
45
|
+
"GetUserSettings",
|
|
46
|
+
lambda: web.get_user_settings(userSettingsArn=portal["userSettingsArn"]).get(
|
|
47
|
+
"userSettings", {}
|
|
48
|
+
),
|
|
49
|
+
default={},
|
|
50
|
+
)
|
|
51
|
+
# Keep the policy-relevant flags; drop bulky/identifying fields.
|
|
52
|
+
for key in (
|
|
53
|
+
"copyAllowed",
|
|
54
|
+
"pasteAllowed",
|
|
55
|
+
"downloadAllowed",
|
|
56
|
+
"uploadAllowed",
|
|
57
|
+
"printAllowed",
|
|
58
|
+
"deepLinkAllowed",
|
|
59
|
+
"webAuthnAllowed",
|
|
60
|
+
"disconnectTimeoutInMinutes",
|
|
61
|
+
"idleDisconnectTimeoutInMinutes",
|
|
62
|
+
):
|
|
63
|
+
if key in (us or {}):
|
|
64
|
+
user_settings[key] = us[key]
|
|
65
|
+
|
|
66
|
+
network: dict[str, object] = {}
|
|
67
|
+
if portal.get("networkSettingsArn"):
|
|
68
|
+
ns = try_call(
|
|
69
|
+
errors,
|
|
70
|
+
consts.PRODUCT_SECURE_BROWSER,
|
|
71
|
+
"GetNetworkSettings",
|
|
72
|
+
lambda: web.get_network_settings(networkSettingsArn=portal["networkSettingsArn"]).get(
|
|
73
|
+
"networkSettings", {}
|
|
74
|
+
),
|
|
75
|
+
default={},
|
|
76
|
+
)
|
|
77
|
+
for key in ("vpcId", "subnetIds", "securityGroupIds"):
|
|
78
|
+
if key in (ns or {}):
|
|
79
|
+
network[key] = ns[key]
|
|
80
|
+
|
|
81
|
+
return SecureBrowserPortalDetails(
|
|
82
|
+
portal_arn=portal_arn,
|
|
83
|
+
display_name=portal.get("displayName"),
|
|
84
|
+
authentication_type=portal.get("authenticationType"),
|
|
85
|
+
status=portal.get("portalStatus"),
|
|
86
|
+
user_settings=user_settings,
|
|
87
|
+
network=network,
|
|
88
|
+
has_browser_policy=bool(portal.get("browserSettingsArn")),
|
|
89
|
+
has_data_protection=bool(portal.get("dataProtectionSettingsArn")),
|
|
90
|
+
errors=errors,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _portal_id(portal: str) -> str:
|
|
95
|
+
"""Accept a portal ARN or id; the CloudWatch dimension uses the id (last ARN segment)."""
|
|
96
|
+
return portal.rsplit("/", 1)[-1] if "/" in portal else portal
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_secure_browser_portal_usage_core(
|
|
100
|
+
factory: ClientFactory,
|
|
101
|
+
portal: str,
|
|
102
|
+
region: str | None,
|
|
103
|
+
lookback_days: int = 7,
|
|
104
|
+
period_hours: int = 24,
|
|
105
|
+
) -> UsageHistory:
|
|
106
|
+
errors: list[ServiceError] = []
|
|
107
|
+
cloudwatch = factory.client(consts.CLOUDWATCH_API, region=region)
|
|
108
|
+
metrics = try_call(
|
|
109
|
+
errors,
|
|
110
|
+
"Amazon CloudWatch",
|
|
111
|
+
"GetMetricData",
|
|
112
|
+
lambda: _fetch_metric_series(
|
|
113
|
+
cloudwatch,
|
|
114
|
+
consts.SECURE_BROWSER_NAMESPACE,
|
|
115
|
+
consts.SECURE_BROWSER_PORTAL_DIMENSION,
|
|
116
|
+
_portal_id(portal),
|
|
117
|
+
consts.SECURE_BROWSER_SESSION_METRICS,
|
|
118
|
+
lookback_days,
|
|
119
|
+
period_hours,
|
|
120
|
+
),
|
|
121
|
+
default={},
|
|
122
|
+
)
|
|
123
|
+
summary = (
|
|
124
|
+
"No session metrics in the window. Secure Browser only emits CloudWatch metrics when "
|
|
125
|
+
"sessions occur (idle portals publish nothing); for detailed usage enable the portal's "
|
|
126
|
+
"Session Logger."
|
|
127
|
+
if not metrics
|
|
128
|
+
else f"Session metrics over {lookback_days}d: see series."
|
|
129
|
+
)
|
|
130
|
+
return UsageHistory(
|
|
131
|
+
target_type=consts.PRODUCT_SECURE_BROWSER,
|
|
132
|
+
target_id=portal,
|
|
133
|
+
lookback_days=lookback_days,
|
|
134
|
+
period_hours=period_hours,
|
|
135
|
+
metrics=metrics or {},
|
|
136
|
+
summary=summary,
|
|
137
|
+
errors=errors,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def register(mcp: Any, factory: ClientFactory) -> None:
|
|
142
|
+
"""Register Secure Browser tools on the FastMCP app."""
|
|
143
|
+
|
|
144
|
+
async def get_secure_browser_portal_details(
|
|
145
|
+
portal_arn: str, region: str | None = None
|
|
146
|
+
) -> dict[str, Any]:
|
|
147
|
+
"""Resolve a WorkSpaces Secure Browser portal's settings (security-relevant).
|
|
148
|
+
|
|
149
|
+
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.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
portal_arn: The portal ARN (from get_euc_inventory_summary / generate_inventory_report).
|
|
155
|
+
region: AWS region. Defaults to the server's configured region.
|
|
156
|
+
"""
|
|
157
|
+
details = get_secure_browser_portal_details_core(
|
|
158
|
+
factory, portal_arn, region or factory.region
|
|
159
|
+
)
|
|
160
|
+
return details.model_dump()
|
|
161
|
+
|
|
162
|
+
async def get_secure_browser_portal_usage(
|
|
163
|
+
portal: str,
|
|
164
|
+
region: str | None = None,
|
|
165
|
+
lookback_days: int = 7,
|
|
166
|
+
period_hours: int = 24,
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
"""Get a Secure Browser portal's session metrics (AWS/WorkSpacesWeb) over a window.
|
|
169
|
+
|
|
170
|
+
NOTE: Secure Browser only emits these metrics when sessions occur, so idle portals return
|
|
171
|
+
nothing; richer per-session data is available via the portal's Session Logger. Read-only.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
portal: The portal id or ARN.
|
|
175
|
+
region: AWS region. Defaults to the server's configured region.
|
|
176
|
+
lookback_days: Window length (default 7).
|
|
177
|
+
period_hours: Bucket size in hours (default 24).
|
|
178
|
+
"""
|
|
179
|
+
usage = get_secure_browser_portal_usage_core(
|
|
180
|
+
factory, portal, region or factory.region, lookback_days, period_hours
|
|
181
|
+
)
|
|
182
|
+
return usage.model_dump()
|
|
183
|
+
|
|
184
|
+
mcp.add_tool(
|
|
185
|
+
get_secure_browser_portal_details,
|
|
186
|
+
annotations=read_only("Secure Browser portal details"),
|
|
187
|
+
)
|
|
188
|
+
mcp.add_tool(
|
|
189
|
+
get_secure_browser_portal_usage, annotations=read_only("Secure Browser portal usage")
|
|
190
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: workspaces-euc-mcp-server
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core).
|
|
5
|
+
Project-URL: Homepage, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp/issues
|
|
8
|
+
Author: bengroeneveldsg
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: amazon-workspaces,appstream,aws,end-user-computing,euc,mcp,model-context-protocol
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: boto3>=1.34.0
|
|
19
|
+
Requires-Dist: loguru>=0.7.2
|
|
20
|
+
Requires-Dist: mcp>=1.2.0
|
|
21
|
+
Requires-Dist: pydantic>=2.6.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: bandit>=1.7; extra == 'dev'
|
|
24
|
+
Requires-Dist: pre-commit>=3.6; extra == 'dev'
|
|
25
|
+
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# WorkSpaces EUC MCP Server
|
|
32
|
+
|
|
33
|
+
[](https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp/actions/workflows/ci.yml)
|
|
34
|
+
|
|
35
|
+
An [MCP](https://modelcontextprotocol.io) server that gives administrators AI-assisted
|
|
36
|
+
**inventory, troubleshooting, and cost/utilization optimization** across the Amazon WorkSpaces
|
|
37
|
+
End User Computing (EUC) portfolio:
|
|
38
|
+
|
|
39
|
+
- **Amazon WorkSpaces Personal** — persistent virtual desktops
|
|
40
|
+
- **Amazon WorkSpaces Pools** — non-persistent pooled desktops
|
|
41
|
+
- **Amazon WorkSpaces Applications** — application streaming (formerly AppStream 2.0)
|
|
42
|
+
- **Amazon WorkSpaces Secure Browser** — managed browser (formerly WorkSpaces Web)
|
|
43
|
+
- **Amazon WorkSpaces Core** — partner / bring-your-own VDI integration, incl. **Core Managed
|
|
44
|
+
Instances** (the `workspaces-instances` API). Plain Core partner desktops appear via the standard
|
|
45
|
+
WorkSpaces API and are counted under WorkSpaces Personal.
|
|
46
|
+
|
|
47
|
+
**Legacy names are accepted.** Ask using former names and the tools still route correctly, while
|
|
48
|
+
output always uses the current name: **AppStream / AppStream 2.0 → Amazon WorkSpaces Applications**
|
|
49
|
+
(same service, the `appstream` API), and **WorkSpaces Web → Amazon WorkSpaces Secure Browser**.
|
|
50
|
+
|
|
51
|
+
> Built for the **administrator** persona, following the
|
|
52
|
+
> [official AWS MCP design conventions](https://github.com/awslabs/mcp) (Python, FastMCP,
|
|
53
|
+
> Pydantic, boto3). It is **read-only by default** and **security-first**: write/lifecycle tools
|
|
54
|
+
> are opt-in, gated behind explicit flags and matching IAM permissions.
|
|
55
|
+
|
|
56
|
+
## Why this exists
|
|
57
|
+
|
|
58
|
+
Generic AWS MCP servers can already call EUC APIs one-to-one. This server is different: its tools
|
|
59
|
+
are **cross-service workflows** that synthesize a result (an inventory rollup, a connectivity
|
|
60
|
+
diagnosis, a right-sizing recommendation) instead of returning raw API output. See
|
|
61
|
+
[`DESIGN.md`](DESIGN.md) for the full tool inventory and roadmap.
|
|
62
|
+
|
|
63
|
+
## Status
|
|
64
|
+
|
|
65
|
+
**Phase 1 (in progress)** — read-only inventory and troubleshooting tools:
|
|
66
|
+
|
|
67
|
+
| Tool | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `get_euc_inventory_summary` | Cross-service inventory for a region (incl. **WorkSpaces Core Managed Instances**): per-service counts by state, grand total, and any per-service collection errors. |
|
|
70
|
+
| `diagnose_workspace_connectivity` | Why a WorkSpaces Personal desktop may be unreachable — correlates WorkSpace state, connection status, directory health, and CloudWatch connection metrics into a ranked diagnosis. |
|
|
71
|
+
| `diagnose_application_fleet` | A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
|
|
72
|
+
| `diagnose_pool` | A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
|
|
73
|
+
| `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). |
|
|
74
|
+
| `check_directory_health` | Registration state and AWS Directory Service stage for one or all WorkSpaces-registered directories. |
|
|
75
|
+
| `analyze_workspace_utilization` | Classifies WorkSpaces Personal desktops as unused / idle / active from the `UserConnected` metric (Tier 1). |
|
|
76
|
+
| `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). |
|
|
77
|
+
| `get_workspace_performance` | Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from `AWS/WorkSpaces` — no CloudWatch agent (Tier 0). |
|
|
78
|
+
| `get_workspace_connection_history` | A desktop's connection/session **history** (UserConnected + connection attempts/failures) over a window, with a summary (Tier 0). |
|
|
79
|
+
| `get_pool_session_history` | A WorkSpaces Pool's user-**session history** (active/available/utilization capacity time-series), flags idle pool capacity (Tier 0). |
|
|
80
|
+
| `recommend_bundle_rightsizing` | Suggests smaller/larger compute types from CPU & memory headroom (general families; graphics excluded) (Tier 0). |
|
|
81
|
+
| `get_euc_cost_summary` | EUC spend by service over a window via Cost Explorer, account-wide (Tier 1). |
|
|
82
|
+
| `generate_inventory_report` | Detailed per-resource inventory (desktops **with assigned user / computer name / IP**, pools, fleets, **stacks + their associated fleets**, portals) with key attributes (Tier 0). |
|
|
83
|
+
| `audit_security_posture` | Cross-service: flags unencrypted WorkSpace volumes, directories without IP access control groups, and **Secure Browser portals / Applications stacks that allow data egress** (clipboard/download/print) (Tier 0). |
|
|
84
|
+
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
|
|
85
|
+
| `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). |
|
|
86
|
+
| `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
|
|
87
|
+
|
|
88
|
+
Cost/utilization tools need the **Tier 1** IAM policy ([`iam/tier1-cost.json`](iam/tier1-cost.json));
|
|
89
|
+
everything else above is **Tier 0** ([`iam/tier0-diagnostics.json`](iam/tier0-diagnostics.json)).
|
|
90
|
+
|
|
91
|
+
### Write tools — opt-in, guarded (Phase 2, Tier 2)
|
|
92
|
+
|
|
93
|
+
Registered **only** when launched with `--enable-writes`, and need the **Tier 2** policy
|
|
94
|
+
([`iam/tier2-lifecycle.json`](iam/tier2-lifecycle.json)). Every mutation is **dry-run by default**
|
|
95
|
+
(returns a plan, changes nothing) unless called with `confirm=true`, and confirmed bulk actions are
|
|
96
|
+
**refused above `--max-bulk-targets`**.
|
|
97
|
+
|
|
98
|
+
| Tool | Description |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| `start_workspaces` / `stop_workspaces` / `reboot_workspaces` | Power operations on WorkSpaces Personal desktops (batch, capped). |
|
|
101
|
+
| `modify_workspace_running_mode` | Switch a desktop between `AUTO_STOP` and `ALWAYS_ON`. |
|
|
102
|
+
| `start_workspaces_pool` / `stop_workspaces_pool` | Power a WorkSpaces Pool on/off. |
|
|
103
|
+
| `update_workspaces_pool_capacity` | Set a Pool's desired user-session capacity. |
|
|
104
|
+
| `start_application_fleet` / `stop_application_fleet` | Power a WorkSpaces Applications fleet on/off. |
|
|
105
|
+
| `update_application_fleet_capacity` | Set a fleet's desired instance capacity. |
|
|
106
|
+
|
|
107
|
+
### Destructive tools — double opt-in, typed acknowledgement (Phase 3, Tier 3)
|
|
108
|
+
|
|
109
|
+
Registered **only** with both `--enable-writes` **and** `--enable-destructive`, and need the
|
|
110
|
+
**Tier 3** policy ([`iam/tier3-destructive.json`](iam/tier3-destructive.json)). On top of the
|
|
111
|
+
dry-run default and blast-radius cap, each execution requires an **exact typed acknowledgement**.
|
|
112
|
+
|
|
113
|
+
| Tool | Description | Acknowledge |
|
|
114
|
+
|------|-------------|-------------|
|
|
115
|
+
| `terminate_workspaces` | **Permanently delete** desktops (irreversible). | `"TERMINATE"` |
|
|
116
|
+
| `rebuild_workspaces` | Reset root volume to bundle; user volume from last snapshot. | `"REBUILD"` |
|
|
117
|
+
| `restore_workspace` | Restore a desktop from its last snapshot. | `"RESTORE"` |
|
|
118
|
+
|
|
119
|
+
Example: `terminate_workspaces(workspace_ids=[...], confirm=true, acknowledge="TERMINATE")`.
|
|
120
|
+
Without the exact phrase the call is **refused** and nothing changes. See [`DESIGN.md`](DESIGN.md).
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
- Python 3.11+
|
|
125
|
+
- AWS credentials available via the standard chain (`AWS_PROFILE`, `AWS_REGION`, SSO, or an
|
|
126
|
+
assumed role).
|
|
127
|
+
- An IAM identity with the **Tier 0** policy in [`iam/tier0-diagnostics.json`](iam/tier0-diagnostics.json).
|
|
128
|
+
|
|
129
|
+
## Credentials & data handling
|
|
130
|
+
|
|
131
|
+
This server is built to be redistributed and run by many parties, so it **never stores or embeds
|
|
132
|
+
any user-specific data**:
|
|
133
|
+
|
|
134
|
+
- **Credentials** come only from the standard AWS chain at runtime — they are never read into,
|
|
135
|
+
logged by, or persisted by the server.
|
|
136
|
+
- **No state on disk.** There is no config/cache/state file; boto3 clients live in memory only.
|
|
137
|
+
- **No account-specific data in the code** — no account IDs, ARNs, profile names, or regions are
|
|
138
|
+
hardcoded. Provide them at runtime via flags/env. Documentation uses placeholders only.
|
|
139
|
+
|
|
140
|
+
Bring your own credentials and region; the server holds nothing.
|
|
141
|
+
|
|
142
|
+
## Install
|
|
143
|
+
|
|
144
|
+
With [`uv`](https://docs.astral.sh/uv/) (recommended once published):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uvx workspaces-euc-mcp-server@latest
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
From source:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python -m venv .venv
|
|
154
|
+
# Windows: .venv\Scripts\Activate.ps1 | macOS/Linux: source .venv/bin/activate
|
|
155
|
+
pip install -e ".[dev]"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Configure your MCP client
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"mcpServers": {
|
|
163
|
+
"workspaces-euc": {
|
|
164
|
+
"command": "uvx",
|
|
165
|
+
"args": ["workspaces-euc-mcp-server@latest"],
|
|
166
|
+
"env": {
|
|
167
|
+
"AWS_PROFILE": "your-euc-admin-profile",
|
|
168
|
+
"AWS_REGION": "us-east-1"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Running from a source checkout instead of `uvx`:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"mcpServers": {
|
|
180
|
+
"workspaces-euc": {
|
|
181
|
+
"command": "python",
|
|
182
|
+
"args": ["-m", "workspaces_euc_mcp_server.server", "--region", "us-east-1"],
|
|
183
|
+
"env": { "AWS_PROFILE": "your-euc-admin-profile" }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Command-line flags
|
|
190
|
+
|
|
191
|
+
| Flag | Default | Purpose |
|
|
192
|
+
|------|---------|---------|
|
|
193
|
+
| `--region` | session/profile region | Target AWS region. |
|
|
194
|
+
| `--profile` | default chain | AWS named profile. |
|
|
195
|
+
| `--assume-role-arn` | none | Cross-account role to assume (multi-account / MSP). |
|
|
196
|
+
| `--external-id` | none | ExternalId for the assumed role, if required. |
|
|
197
|
+
| `--enable-writes` | off | Register Phase 2 lifecycle (write) tools. |
|
|
198
|
+
| `--enable-destructive` | off | Allow terminate/rebuild/restore (requires `--enable-writes`). |
|
|
199
|
+
| `--max-bulk-targets` | 25 | Blast-radius cap for bulk mutations (Phase 2). |
|
|
200
|
+
|
|
201
|
+
The server starts **read-only**; mutating tools require both the launch flag **and** the matching
|
|
202
|
+
IAM tier.
|
|
203
|
+
|
|
204
|
+
## Multi-account / MSP
|
|
205
|
+
|
|
206
|
+
To manage a **different** account from the one your credentials live in, launch with
|
|
207
|
+
`--assume-role-arn` (and `--external-id` if the role requires it):
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
workspaces-euc-mcp-server --region ap-southeast-1 \
|
|
211
|
+
--assume-role-arn arn:aws:iam::222222222222:role/EucReadOnly --external-id my-ext-id
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The server transparently `sts:AssumeRole`s into the target account and auto-refreshes the
|
|
215
|
+
credentials. Requirements:
|
|
216
|
+
- The launching identity needs `sts:AssumeRole` on the target role.
|
|
217
|
+
- The **target role** needs the matching tier policy from [`iam/`](iam/) (Tier 0 for read-only).
|
|
218
|
+
- To manage many accounts, run one instance per target role (or point separate MCP-client entries
|
|
219
|
+
at different `--assume-role-arn` values).
|
|
220
|
+
|
|
221
|
+
## IAM
|
|
222
|
+
|
|
223
|
+
Attach [`iam/tier0-diagnostics.json`](iam/tier0-diagnostics.json) to the identity the server runs
|
|
224
|
+
as (or to the role you assume with `--assume-role-arn`). Tiers are additive and documented in [`iam/README.md`](iam/README.md). All actions are
|
|
225
|
+
captured by AWS CloudTrail.
|
|
226
|
+
|
|
227
|
+
## Example admin questions
|
|
228
|
+
|
|
229
|
+
Once the server is connected to an MCP client, you can ask in natural language and the client will
|
|
230
|
+
pick the right tool. A few examples:
|
|
231
|
+
|
|
232
|
+
| You ask… | Tool the client uses |
|
|
233
|
+
|----------|----------------------|
|
|
234
|
+
| "What WorkSpaces resources do I have in us-east-1?" | `get_euc_inventory_summary` |
|
|
235
|
+
| "User X can't connect to ws-abc123 — why?" | `diagnose_workspace_connectivity` |
|
|
236
|
+
| "Is the marketing AppStream fleet healthy / out of capacity?" | `diagnose_application_fleet` |
|
|
237
|
+
| "Which desktops are idle or unused this fortnight?" | `analyze_workspace_utilization` / `list_unused_resources` |
|
|
238
|
+
| "Where can I cut WorkSpaces cost?" | `recommend_running_mode` + `get_euc_cost_summary` |
|
|
239
|
+
| "Any desktops without volume encryption or IP restrictions?" | `audit_security_posture` |
|
|
240
|
+
| "Switch ws-abc123 to AutoStop." | `modify_workspace_running_mode` (dry-run first) |
|
|
241
|
+
| "Reboot these three stuck desktops." | `reboot_workspaces` (dry-run, then `confirm=true`) |
|
|
242
|
+
|
|
243
|
+
**Write/destructive tools are off unless enabled.** Mutations show a dry-run plan first; to execute
|
|
244
|
+
you pass `confirm=true` (and, for destructive ops, the exact acknowledge phrase). For example, a
|
|
245
|
+
full destructive run looks like:
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
terminate_workspaces(workspace_ids=["ws-abc123"], confirm=true, acknowledge="TERMINATE")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Launch with writes/destructive enabled:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
workspaces-euc-mcp-server --enable-writes # Tier 2 power/capacity tools
|
|
255
|
+
workspaces-euc-mcp-server --enable-writes --enable-destructive # + Tier 3 terminate/rebuild/restore
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pip install -e ".[dev]"
|
|
262
|
+
pytest # run tests (deterministic, no AWS account needed)
|
|
263
|
+
ruff check . # lint
|
|
264
|
+
ruff format . # format
|
|
265
|
+
pyright # type check
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
Apache-2.0. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
workspaces_euc_mcp_server/__init__.py,sha256=vN9aSy6oLrmsT8LGOYSsuc_NkIQPBtj0XKebpNbL4rg,351
|
|
2
|
+
workspaces_euc_mcp_server/clients.py,sha256=JgLC2ohayDGCnmRetkEKKC03ropxBfgPADrVEOMTHiw,4310
|
|
3
|
+
workspaces_euc_mcp_server/consts.py,sha256=by0n12xjbtGoFVR_9kF0tTeHa9AUt7am3RAVtfrEXDI,7653
|
|
4
|
+
workspaces_euc_mcp_server/models.py,sha256=H9S4Ocyb6cHf1DnZU6BPqzpMmyTeorCY8IxCo_qWfiQ,11697
|
|
5
|
+
workspaces_euc_mcp_server/server.py,sha256=cDz3bAWx5vbtRHxDdFxwvgSeQea4FZ2fN_A9kqzoMkY,4201
|
|
6
|
+
workspaces_euc_mcp_server/tools/__init__.py,sha256=BXnj7fvAQksDdnNkwRIK_AqbEJbdBZHxCZNdo0rSpeE,297
|
|
7
|
+
workspaces_euc_mcp_server/tools/_common.py,sha256=rOOQw3VZwKXe7LPAfIUgyABH930jCTx6EMlseNBUqjM,2897
|
|
8
|
+
workspaces_euc_mcp_server/tools/cost.py,sha256=uy3vczQXpLAOgy3LKogy4qPrxZGeWK8xrLs01QHM1cg,12283
|
|
9
|
+
workspaces_euc_mcp_server/tools/destructive.py,sha256=FqEbZT8gn0RJatSyYP3bCBaFWARRwRONgURynTOli7Y,10538
|
|
10
|
+
workspaces_euc_mcp_server/tools/diagnostics.py,sha256=YZ_-NE-smwNvuI-9rGRZQHX0wG3sKCpAiUyZ_5c_tv4,29409
|
|
11
|
+
workspaces_euc_mcp_server/tools/inventory.py,sha256=zW73ZgcvczAZnUfQ18lImZwcOj9VcXzm9HUyBdIG3Bs,5607
|
|
12
|
+
workspaces_euc_mcp_server/tools/lifecycle.py,sha256=8SkLAVw06Ssop4GQemgazJNBO_vqhEn8Po0kJAj1L2Q,20470
|
|
13
|
+
workspaces_euc_mcp_server/tools/performance.py,sha256=esTptr_6qPEo2LeTp02CHRwnEn212N0GTL1qh0Hrhm8,23770
|
|
14
|
+
workspaces_euc_mcp_server/tools/pricing.py,sha256=VLUc2Uwbey9pLrDSlUZdDdIn6vN4RgTMSvB1i___Wo0,6042
|
|
15
|
+
workspaces_euc_mcp_server/tools/reporting.py,sha256=vIwrW_pSxASdBcd3PusXjMiZmNXGaN5JSHU7GxQV0ZU,20839
|
|
16
|
+
workspaces_euc_mcp_server/tools/secure_browser.py,sha256=Zo9u1OELoJ4j67_NgaNdTvmQuYS4SxRFY9wcbmiLgpI,7087
|
|
17
|
+
workspaces_euc_mcp_server-0.1.1.dist-info/METADATA,sha256=RDH6DL6TQKo1g3uJi7b9D8-StQ503u4gDFzj8L9nbkc,14056
|
|
18
|
+
workspaces_euc_mcp_server-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
19
|
+
workspaces_euc_mcp_server-0.1.1.dist-info/entry_points.txt,sha256=1DxnZ045NekJuyDPYZb4GVBuNBFZ23tapUe4u3oK5Pc,84
|
|
20
|
+
workspaces_euc_mcp_server-0.1.1.dist-info/licenses/LICENSE,sha256=23MygUoAYVsCVggnjcHMUxSCCbqqTXEHPl9nysgvl54,11357
|
|
21
|
+
workspaces_euc_mcp_server-0.1.1.dist-info/RECORD,,
|