MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core).
Project description
WorkSpaces EUC MCP Server
Disclaimer: This project is not an official AWS product, service, or solution, and is not affiliated with or endorsed by Amazon Web Services. It is an independent, community example implementation intended to demonstrate what is possible when combining the Amazon WorkSpaces End User Computing APIs with the Model Context Protocol. It is provided as a starting point to learn from, adapt, and iterate on — not as a supported or production-certified offering. Use it at your own discretion and always validate it against your organisation's security, compliance, and operational requirements before deploying to production.
An MCP server that gives administrators AI-assisted inventory, troubleshooting, and cost/utilization optimization across the Amazon WorkSpaces End User Computing (EUC) portfolio:
- Amazon WorkSpaces Personal — persistent virtual desktops
- Amazon WorkSpaces Pools — non-persistent pooled desktops
- Amazon WorkSpaces Applications — application streaming (formerly AppStream 2.0)
- Amazon WorkSpaces Secure Browser — managed browser (formerly WorkSpaces Web)
- Amazon WorkSpaces Core — partner / bring-your-own VDI integration, incl. Core Managed
Instances (the
workspaces-instancesAPI). Plain Core partner desktops appear via the standard WorkSpaces API and are counted under WorkSpaces Personal.
Legacy names are accepted. Ask using former names and the tools still route correctly, while
output always uses the current name: AppStream / AppStream 2.0 → Amazon WorkSpaces Applications
(same service, the appstream API), and WorkSpaces Web → Amazon WorkSpaces Secure Browser.
Built for the administrator persona, following the official AWS MCP design conventions (Python, FastMCP, Pydantic, boto3). It is read-only by default and security-first: write/lifecycle tools are opt-in, gated behind explicit flags and matching IAM permissions.
Why this exists
Generic AWS MCP servers can already call EUC APIs one-to-one. This server is different: its tools
are cross-service workflows that synthesize a result (an inventory rollup, a connectivity
diagnosis, a right-sizing recommendation) instead of returning raw API output. See
DESIGN.md for the full tool inventory and roadmap.
Status
Phase 1 (in progress) — read-only inventory and troubleshooting tools:
| Tool | Description |
|---|---|
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. |
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. |
diagnose_application_fleet |
A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
diagnose_pool |
A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
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). |
check_directory_health |
Registration state and AWS Directory Service stage for one or all WorkSpaces-registered directories. |
analyze_workspace_utilization |
Classifies WorkSpaces Personal desktops as unused / idle / active from the UserConnected metric (Tier 1). |
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). |
get_workspace_performance |
Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from AWS/WorkSpaces — no CloudWatch agent (Tier 0). |
get_workspace_connection_history |
A desktop's connection/session history (UserConnected + connection attempts/failures) over a window, with a summary (Tier 0). |
get_pool_session_history |
A WorkSpaces Pool's user-session history (active/available/utilization capacity time-series), flags idle pool capacity (Tier 0). |
recommend_bundle_rightsizing |
Suggests smaller/larger compute types from CPU & memory headroom (general families; graphics excluded) (Tier 0). |
get_euc_cost_summary |
EUC spend by service over a window (or an explicit start_date/end_date calendar month) via Cost Explorer, account-wide. Services are matched by keyword so naming variants (e.g. AppStream 2.0) aren't dropped; note Cost Explorer bills WorkSpaces Personal/Pools/Core together as one "Amazon WorkSpaces" line (Tier 1). |
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). |
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). |
get_secure_browser_portal_details |
Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
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). |
list_unused_resources |
Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
Cost/utilization tools need the Tier 1 IAM policy (iam/tier1-cost.json);
everything else above is Tier 0 (iam/tier0-diagnostics.json).
Write tools — opt-in, guarded (Phase 2, Tier 2)
Registered only when launched with --enable-writes, and need the Tier 2 policy
(iam/tier2-lifecycle.json). Every mutation is dry-run by default
(returns a plan, changes nothing) unless called with confirm=true, and confirmed bulk actions are
refused above --max-bulk-targets.
| Tool | Description |
|---|---|
start_workspaces / stop_workspaces / reboot_workspaces |
Power operations on WorkSpaces Personal desktops (batch, capped). |
modify_workspace_running_mode |
Switch a desktop between AUTO_STOP and ALWAYS_ON. |
start_workspaces_pool / stop_workspaces_pool |
Power a WorkSpaces Pool on/off. |
update_workspaces_pool_capacity |
Set a Pool's desired user-session capacity. |
start_application_fleet / stop_application_fleet |
Power a WorkSpaces Applications fleet on/off. |
update_application_fleet_capacity |
Set a fleet's desired instance capacity. |
Destructive tools — double opt-in, typed acknowledgement (Phase 3, Tier 3)
Registered only with both --enable-writes and --enable-destructive, and need the
Tier 3 policy (iam/tier3-destructive.json). On top of the
dry-run default and blast-radius cap, each execution requires an exact typed acknowledgement.
| Tool | Description | Acknowledge |
|---|---|---|
terminate_workspaces |
Permanently delete desktops (irreversible). | "TERMINATE" |
rebuild_workspaces |
Reset root volume to bundle; user volume from last snapshot. | "REBUILD" |
restore_workspace |
Restore a desktop from its last snapshot. | "RESTORE" |
Example: terminate_workspaces(workspace_ids=[...], confirm=true, acknowledge="TERMINATE").
Without the exact phrase the call is refused and nothing changes. See DESIGN.md.
Requirements
- Python 3.11+
- AWS credentials available via the standard chain (
AWS_PROFILE,AWS_REGION, SSO, or an assumed role). - An IAM identity with the Tier 0 policy in
iam/tier0-diagnostics.json.
Credentials & data handling
This server is built to be redistributed and run by many parties, so it never stores or embeds any user-specific data:
- Credentials come only from the standard AWS chain at runtime — they are never read into, logged by, or persisted by the server.
- No state on disk. There is no config/cache/state file; boto3 clients live in memory only.
- No account-specific data in the code — no account IDs, ARNs, profile names, or regions are hardcoded. Provide them at runtime via flags/env. Documentation uses placeholders only.
Bring your own credentials and region; the server holds nothing.
Install
With uv (recommended):
uvx workspaces-euc-mcp-server@latest
Or with pip:
pip install workspaces-euc-mcp-server
Or with Docker (published to GHCR; the server speaks MCP over stdio, so run with -i):
docker run -i --rm \
-e AWS_PROFILE=your-euc-admin-profile -e AWS_REGION=us-east-1 \
-v "$HOME/.aws:/home/mcp/.aws:ro" \
ghcr.io/bengroeneveldsg/aws-workspaces-euc-mcp:latest --region us-east-1
From source (for development):
python -m venv .venv
# Windows: .venv\Scripts\Activate.ps1 | macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"
Configure your MCP client
{
"mcpServers": {
"workspaces-euc": {
"command": "uvx",
"args": ["workspaces-euc-mcp-server@latest"],
"env": {
"AWS_PROFILE": "your-euc-admin-profile",
"AWS_REGION": "us-east-1"
}
}
}
}
Running from a source checkout instead of uvx:
{
"mcpServers": {
"workspaces-euc": {
"command": "python",
"args": ["-m", "workspaces_euc_mcp_server.server", "--region", "us-east-1"],
"env": { "AWS_PROFILE": "your-euc-admin-profile" }
}
}
}
The two blocks above are the standard mcpServers shape used by most clients (Claude Desktop,
Cursor, etc.). Some clients use a form instead of raw JSON — see the example below.
Example: Amazon Quick (Desktop)
Amazon Quick's Capabilities → MCP → Add MCP uses a form rather than JSON. Choose connection type Local ("Run a command on your machine") and fill in:
| Field | Value |
|---|---|
| Name | WorkSpaces EUC |
| Command | uvx |
| Arguments | workspaces-euc-mcp-server@latest --region us-east-1 |
| Description | Admin tools for Amazon WorkSpaces EUC — inventory, troubleshooting, cost/utilization. Read-only. |
| Environment variables | AWS_PROFILE = your-euc-admin-profile · AWS_REGION = us-east-1 |
| Timeout (seconds) | 60 |
Notes:
- Set
--region(in Arguments) andAWS_REGIONto where your WorkSpaces actually live; keep the two in sync. - Bump the timeout from the default 30 → 60–120 for the first run —
uvxdownloads the package on first launch, which can exceed 30 s and look like a failed connection. Later starts are fast. - If
uvxisn't on yourPATH, use Commandworkspaces-euc-mcp-server(afterpip install workspaces-euc-mcp-server) with Arguments--region us-east-1, or Commandpythonwith Arguments-m workspaces_euc_mcp_server.server --region us-east-1. - To enable writes/destructive, append the flags to Arguments (e.g.
… --enable-writes --max-bulk-targets 10) — and attach the matching IAM tier (see the safety gates). - A local MCP server has no "Sign in" button (that's a Quick Connections feature). It uses
your AWS credential chain, so authenticate first — e.g.
aws sso login --profile your-profilefor SSO — then the server connects. See AWS authentication.
AWS authentication
The server uses the standard AWS credential chain — it does not handle sign-in itself. Provide credentials however boto3 expects them:
- IAM Identity Center (SSO): configure an
sso-sessionprofile, thenaws sso login --profile <name>. With the modernsso-sessionformat, botocore auto-refreshes the token for the session window, so you log in once rather than every few hours. (Signing into the AWS Console or access portal in a browser does not create the on-disk token the server needs — onlyaws sso logindoes.) - Static / temporary keys: a named profile in
~/.aws/credentials, orAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENenv vars. - Assumed role / multi-account: see Multi-account / MSP.
If calls fail with an expired-token / UnauthorizedException error, your session has lapsed —
re-authenticate (for SSO, aws sso login --profile <name>) and retry. Nothing in the MCP config
changes.
Enabling write / destructive tools — the safety gates
The config above is read-only: the write and destructive tools are not even registered, so
they cannot be called no matter what the model or the IAM policy allows. Enabling them is a
deliberate act, and IAM permissions alone are not enough. A destructive call (e.g.
terminate_workspaces) only runs after clearing all four of these independent gates:
| # | Gate | Where it lives | What it does |
|---|---|---|---|
| 1 | Launch flag | The args in your MCP-client config |
Destructive tools aren't registered unless the server was started with both --enable-writes and --enable-destructive. Default launch = read-only. |
| 2 | IAM tier | The AWS profile / assumed role | The credentials must carry the matching tier (iam/tier2-lifecycle.json for writes, iam/tier3-destructive.json for destructive). Without it, AWS denies the call. |
| 3 | confirm=true |
The tool call | Every mutation is dry-run by default — it returns a plan and changes nothing. You must explicitly pass confirm=true. |
| 4 | Typed acknowledgement + blast-radius cap | The tool call | Destructive ops also require the exact phrase (acknowledge="TERMINATE" / "REBUILD" / "RESTORE") and must stay within --max-bulk-targets. Wrong phrase or too many targets → refused, nothing changes. |
The launch flag grants no AWS permission. Gate 1 (the
--enable-*flag) and Gate 2 (IAM) are two separate switches and you need both. Turning on--enable-destructiveonly exposes the tool in the client — it does not give your AWS profile any access. If the flag is on but the profile/role is missing the matching tier policy, the tool is callable and its dry-run works, but the realconfirm=truecall fails with an AWSAccessDeniederror and nothing is deleted. So enabling writes/destructive in your MCP client is necessary but not sufficient: you must also attachiam/tier2-lifecycle.json/iam/tier3-destructive.jsonto the AWS profile (or assumed role) the server runs as.Gates 1–2 are the real security boundary (config + AWS-enforced IAM). Gates 3–4 are in-tool guardrails against an over-eager agent or a fat-fingered single call — they are not a substitute for IAM. The genuine least-privilege control is: don't grant Tier 2/3 and don't pass the flags unless you truly want those operations available. For an extra backstop, scope the Tier 2/3 policy with resource tags / conditions so even an enabled server can only touch a bounded set of resources.
MCP-client config — writes enabled (Tier 2): add the flag to args and attach the Tier 2 policy.
{
"mcpServers": {
"workspaces-euc": {
"command": "uvx",
"args": ["workspaces-euc-mcp-server@latest", "--enable-writes", "--max-bulk-targets", "10"],
"env": {
"AWS_PROFILE": "your-euc-admin-profile",
"AWS_REGION": "us-east-1"
}
}
}
}
MCP-client config — destructive enabled (Tier 3): both flags, and attach the Tier 3 policy. Use a tightly-scoped profile.
{
"mcpServers": {
"workspaces-euc": {
"command": "uvx",
"args": [
"workspaces-euc-mcp-server@latest",
"--enable-writes",
"--enable-destructive",
"--max-bulk-targets", "5"
],
"env": {
"AWS_PROFILE": "your-euc-admin-profile",
"AWS_REGION": "us-east-1"
}
}
}
}
Even with the destructive config above, a call still does nothing until gates 3–4 are satisfied:
terminate_workspaces(workspace_ids=["ws-abc123"], confirm=true, acknowledge="TERMINATE")
Equivalent CLI launches (e.g. for the Docker entrypoint or a shell):
workspaces-euc-mcp-server --enable-writes --max-bulk-targets 10 # Tier 2 only
workspaces-euc-mcp-server --enable-writes --enable-destructive --max-bulk-targets 5 # + Tier 3
Command-line flags
| Flag | Default | Purpose |
|---|---|---|
--region |
session/profile region | Target AWS region. |
--profile |
default chain | AWS named profile. |
--assume-role-arn |
none | Cross-account role to assume (multi-account / MSP). |
--external-id |
none | ExternalId for the assumed role, if required. |
--enable-writes |
off | Register Phase 2 lifecycle (write) tools. |
--enable-destructive |
off | Allow terminate/rebuild/restore (requires --enable-writes). |
--max-bulk-targets |
25 | Blast-radius cap for bulk mutations (Phase 2). |
The server starts read-only; mutating tools require both the launch flag and the matching IAM tier.
Multi-account / MSP
To manage a different account from the one your credentials live in, launch with
--assume-role-arn (and --external-id if the role requires it):
workspaces-euc-mcp-server --region ap-southeast-1 \
--assume-role-arn arn:aws:iam::222222222222:role/EucReadOnly --external-id my-ext-id
The server transparently sts:AssumeRoles into the target account and auto-refreshes the
credentials. Requirements:
- The launching identity needs
sts:AssumeRoleon the target role. - The target role needs the matching tier policy from
iam/(Tier 0 for read-only). - To manage many accounts, run one instance per target role (or point separate MCP-client entries
at different
--assume-role-arnvalues).
IAM
Attach iam/tier0-diagnostics.json to the identity the server runs
as (or to the role you assume with --assume-role-arn). Tiers are additive and documented in iam/README.md. All actions are
captured by AWS CloudTrail.
Example admin questions
Once the server is connected to an MCP client, you can ask in natural language and the client will pick the right tool. A few examples:
| You ask… | Tool the client uses |
|---|---|
| "What WorkSpaces resources do I have in us-east-1?" | get_euc_inventory_summary |
| "User X can't connect to ws-abc123 — why?" | diagnose_workspace_connectivity |
| "Is the marketing AppStream fleet healthy / out of capacity?" | diagnose_application_fleet |
| "Which desktops are idle or unused this fortnight?" | analyze_workspace_utilization / list_unused_resources |
| "Where can I cut WorkSpaces cost?" | recommend_running_mode + get_euc_cost_summary |
| "Any desktops without volume encryption or IP restrictions?" | audit_security_posture |
| "Switch ws-abc123 to AutoStop." | modify_workspace_running_mode (dry-run first) |
| "Reboot these three stuck desktops." | reboot_workspaces (dry-run, then confirm=true) |
Write/destructive tools are off unless enabled. Mutations show a dry-run plan first; to execute
you pass confirm=true (and, for destructive ops, the exact acknowledge phrase). See
Enabling write / destructive tools — the safety gates
for how to turn them on (MCP-client config + IAM) and the four gates each call must clear.
Development
pip install -e ".[dev]"
pytest # run tests (deterministic, no AWS account needed)
ruff check . # lint
ruff format . # format
pyright # type check
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file workspaces_euc_mcp_server-0.1.4.tar.gz.
File metadata
- Download URL: workspaces_euc_mcp_server-0.1.4.tar.gz
- Upload date:
- Size: 77.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4ffcd7a7e71d8da21fcaea590831d5246ac3959320a1c2d56dd23a6ac35e847
|
|
| MD5 |
4cf320808ef273559c810ad21f1e9e3a
|
|
| BLAKE2b-256 |
e901efdbb3e43f049b34c042c03eacc2dcf39306797bc140448bb0ea9d5f8b95
|
Provenance
The following attestation bundles were made for workspaces_euc_mcp_server-0.1.4.tar.gz:
Publisher:
publish.yml on bengroeneveldsg/aws-workspaces-euc-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
workspaces_euc_mcp_server-0.1.4.tar.gz -
Subject digest:
e4ffcd7a7e71d8da21fcaea590831d5246ac3959320a1c2d56dd23a6ac35e847 - Sigstore transparency entry: 1699980406
- Sigstore integration time:
-
Permalink:
bengroeneveldsg/aws-workspaces-euc-mcp@a36328bcd4cf037dc303941ae8f538e9351b1d63 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/bengroeneveldsg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a36328bcd4cf037dc303941ae8f538e9351b1d63 -
Trigger Event:
release
-
Statement type:
File details
Details for the file workspaces_euc_mcp_server-0.1.4-py3-none-any.whl.
File metadata
- Download URL: workspaces_euc_mcp_server-0.1.4-py3-none-any.whl
- Upload date:
- Size: 62.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c6c2c79a7fffe401e5cd6d171f8a9e0ba0f53a77b2ef792497c8cef968bd143
|
|
| MD5 |
1cea979facdfa14e1164e56ba9dd572a
|
|
| BLAKE2b-256 |
e8ea7533a2b28c8cf824ac1187e08298c8a66ac6da89236add672a59bf23bbb1
|
Provenance
The following attestation bundles were made for workspaces_euc_mcp_server-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on bengroeneveldsg/aws-workspaces-euc-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
workspaces_euc_mcp_server-0.1.4-py3-none-any.whl -
Subject digest:
8c6c2c79a7fffe401e5cd6d171f8a9e0ba0f53a77b2ef792497c8cef968bd143 - Sigstore transparency entry: 1699980474
- Sigstore integration time:
-
Permalink:
bengroeneveldsg/aws-workspaces-euc-mcp@a36328bcd4cf037dc303941ae8f538e9351b1d63 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/bengroeneveldsg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a36328bcd4cf037dc303941ae8f538e9351b1d63 -
Trigger Event:
release
-
Statement type: