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,158 @@
|
|
|
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
|
+
"""Inventory & discovery tools (read-only, IAM Tier 0).
|
|
5
|
+
|
|
6
|
+
These are *workflow* tools: each one fans out across several EUC services and synthesizes a single
|
|
7
|
+
result, rather than mirroring one API call. Collection is best-effort — a failure for one service
|
|
8
|
+
(missing permission, unsupported region) is captured in ``errors`` and does not abort the summary.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .. import consts
|
|
16
|
+
from ..clients import ClientFactory
|
|
17
|
+
from ..models import EucInventorySummary, InventoryError, ServiceInventory
|
|
18
|
+
from ._common import count_by, paginate, read_only, try_call
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def collect_inventory(factory: ClientFactory, region: str | None) -> EucInventorySummary:
|
|
22
|
+
"""Collect a cross-service EUC inventory for one region. Pure/testable core."""
|
|
23
|
+
errors: list[InventoryError] = []
|
|
24
|
+
services: list[ServiceInventory] = []
|
|
25
|
+
|
|
26
|
+
workspaces = factory.client(consts.WORKSPACES_API, region=region)
|
|
27
|
+
|
|
28
|
+
personal = try_call(
|
|
29
|
+
errors,
|
|
30
|
+
consts.PRODUCT_WORKSPACES_PERSONAL,
|
|
31
|
+
"DescribeWorkspaces",
|
|
32
|
+
lambda: paginate(workspaces.describe_workspaces, "Workspaces"),
|
|
33
|
+
)
|
|
34
|
+
if personal is not None:
|
|
35
|
+
services.append(
|
|
36
|
+
ServiceInventory(
|
|
37
|
+
service=consts.PRODUCT_WORKSPACES_PERSONAL,
|
|
38
|
+
resource_type="WorkSpace",
|
|
39
|
+
count=len(personal),
|
|
40
|
+
by_state=count_by(personal, "State"),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
pools = try_call(
|
|
45
|
+
errors,
|
|
46
|
+
consts.PRODUCT_WORKSPACES_POOLS,
|
|
47
|
+
"DescribeWorkspacesPools",
|
|
48
|
+
lambda: paginate(workspaces.describe_workspaces_pools, "WorkspacesPools"),
|
|
49
|
+
)
|
|
50
|
+
if pools is not None:
|
|
51
|
+
services.append(
|
|
52
|
+
ServiceInventory(
|
|
53
|
+
service=consts.PRODUCT_WORKSPACES_POOLS,
|
|
54
|
+
resource_type="WorkSpacesPool",
|
|
55
|
+
count=len(pools),
|
|
56
|
+
by_state=count_by(pools, "State"),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
appstream = factory.client(consts.APPSTREAM_API, region=region)
|
|
61
|
+
fleets = try_call(
|
|
62
|
+
errors,
|
|
63
|
+
consts.PRODUCT_WORKSPACES_APPLICATIONS,
|
|
64
|
+
"DescribeFleets",
|
|
65
|
+
lambda: paginate(appstream.describe_fleets, "Fleets"),
|
|
66
|
+
)
|
|
67
|
+
if fleets is not None:
|
|
68
|
+
services.append(
|
|
69
|
+
ServiceInventory(
|
|
70
|
+
service=consts.PRODUCT_WORKSPACES_APPLICATIONS,
|
|
71
|
+
resource_type="Fleet",
|
|
72
|
+
count=len(fleets),
|
|
73
|
+
by_state=count_by(fleets, "State"),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
stacks = try_call(
|
|
78
|
+
errors,
|
|
79
|
+
consts.PRODUCT_WORKSPACES_APPLICATIONS,
|
|
80
|
+
"DescribeStacks",
|
|
81
|
+
lambda: paginate(appstream.describe_stacks, "Stacks"),
|
|
82
|
+
)
|
|
83
|
+
if stacks is not None:
|
|
84
|
+
services.append(
|
|
85
|
+
ServiceInventory(
|
|
86
|
+
service=consts.PRODUCT_WORKSPACES_APPLICATIONS,
|
|
87
|
+
resource_type="Stack",
|
|
88
|
+
count=len(stacks),
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
secure_browser = factory.client(consts.SECURE_BROWSER_API, region=region)
|
|
93
|
+
portals = try_call(
|
|
94
|
+
errors,
|
|
95
|
+
consts.PRODUCT_SECURE_BROWSER,
|
|
96
|
+
"ListPortals",
|
|
97
|
+
lambda: paginate(
|
|
98
|
+
secure_browser.list_portals,
|
|
99
|
+
"portals",
|
|
100
|
+
pagination_in="nextToken",
|
|
101
|
+
pagination_out="nextToken",
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
if portals is not None:
|
|
105
|
+
services.append(
|
|
106
|
+
ServiceInventory(
|
|
107
|
+
service=consts.PRODUCT_SECURE_BROWSER,
|
|
108
|
+
resource_type="Portal",
|
|
109
|
+
count=len(portals),
|
|
110
|
+
by_state=count_by(portals, "portalStatus"),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
instances_client = factory.client(consts.WORKSPACES_INSTANCES_API, region=region)
|
|
115
|
+
instances = try_call(
|
|
116
|
+
errors,
|
|
117
|
+
consts.PRODUCT_WORKSPACES_CORE_INSTANCES,
|
|
118
|
+
"ListWorkspaceInstances",
|
|
119
|
+
lambda: paginate(instances_client.list_workspace_instances, "WorkspaceInstances"),
|
|
120
|
+
)
|
|
121
|
+
if instances is not None:
|
|
122
|
+
services.append(
|
|
123
|
+
ServiceInventory(
|
|
124
|
+
service=consts.PRODUCT_WORKSPACES_CORE_INSTANCES,
|
|
125
|
+
resource_type="ManagedInstance",
|
|
126
|
+
count=len(instances),
|
|
127
|
+
by_state=count_by(instances, "ProvisionState"),
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
total = sum(s.count for s in services)
|
|
132
|
+
return EucInventorySummary(
|
|
133
|
+
region=region,
|
|
134
|
+
total_resources=total,
|
|
135
|
+
services=services,
|
|
136
|
+
errors=errors,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def register(mcp: Any, factory: ClientFactory) -> None:
|
|
141
|
+
"""Register inventory tools on the FastMCP app."""
|
|
142
|
+
|
|
143
|
+
async def get_euc_inventory_summary(region: str | None = None) -> dict[str, Any]:
|
|
144
|
+
"""Summarize all Amazon WorkSpaces EUC resources in an AWS region.
|
|
145
|
+
|
|
146
|
+
Fans out across WorkSpaces Personal, WorkSpaces Pools, WorkSpaces Applications (formerly
|
|
147
|
+
AppStream 2.0), and WorkSpaces Secure Browser (formerly WorkSpaces Web), returning
|
|
148
|
+
per-service counts broken down by state, the grand total, and any per-service collection
|
|
149
|
+
errors (e.g. missing permissions). Read-only.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
region: AWS region to inventory. Defaults to the server's configured region.
|
|
153
|
+
"""
|
|
154
|
+
target_region = region or factory.region
|
|
155
|
+
summary = collect_inventory(factory, target_region)
|
|
156
|
+
return summary.model_dump()
|
|
157
|
+
|
|
158
|
+
mcp.add_tool(get_euc_inventory_summary, annotations=read_only("EUC inventory summary"))
|