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.
@@ -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"))