awslabs.eks-mcp-server 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -18,7 +18,8 @@ import boto3
18
18
  import os
19
19
  from awslabs.eks_mcp_server import __version__
20
20
  from botocore.config import Config
21
- from typing import Any, Optional
21
+ from loguru import logger
22
+ from typing import Any, Dict, Optional
22
23
 
23
24
 
24
25
  class AwsHelper:
@@ -26,8 +27,17 @@ class AwsHelper:
26
27
 
27
28
  This class provides utility methods for interacting with AWS services,
28
29
  including region and profile management and client creation.
30
+
31
+ This class implements a singleton pattern with a client cache to avoid
32
+ creating multiple clients for the same service.
29
33
  """
30
34
 
35
+ # Singleton instance
36
+ _instance = None
37
+
38
+ # Client cache with AWS service name as key
39
+ _client_cache: Dict[str, Any] = {}
40
+
31
41
  @staticmethod
32
42
  def get_aws_region() -> Optional[str]:
33
43
  """Get the AWS region from the environment if set."""
@@ -40,10 +50,11 @@ class AwsHelper:
40
50
 
41
51
  @classmethod
42
52
  def create_boto3_client(cls, service_name: str, region_name: Optional[str] = None) -> Any:
43
- """Create a boto3 client with the appropriate profile and region.
53
+ """Create or retrieve a cached boto3 client with the appropriate profile and region.
44
54
 
45
55
  The client is configured with a custom user agent suffix 'awslabs/mcp/eks-mcp-server/{version}'
46
- to identify API calls made by the EKS MCP Server.
56
+ to identify API calls made by the EKS MCP Server. Clients are cached to improve performance
57
+ and reduce resource usage.
47
58
 
48
59
  Args:
49
60
  service_name: The AWS service name (e.g., 'ec2', 's3', 'eks')
@@ -51,25 +62,47 @@ class AwsHelper:
51
62
 
52
63
  Returns:
53
64
  A boto3 client for the specified service
65
+
66
+ Raises:
67
+ Exception: If there's an error creating the client
54
68
  """
55
- # Get region from parameter or environment if set
56
- region: Optional[str] = region_name if region_name is not None else cls.get_aws_region()
69
+ try:
70
+ # Get region from parameter or environment if set
71
+ region: Optional[str] = (
72
+ region_name if region_name is not None else cls.get_aws_region()
73
+ )
57
74
 
58
- # Get profile from environment if set
59
- profile = cls.get_aws_profile()
75
+ # Get profile from environment if set
76
+ profile = cls.get_aws_profile()
60
77
 
61
- # Create config with user agent suffix
62
- config = Config(user_agent_extra=f'awslabs/mcp/eks-mcp-server/{__version__}')
78
+ # Use service name as the cache key
79
+ cache_key = service_name
63
80
 
64
- # Create session with profile if specified
65
- if profile:
66
- session = boto3.Session(profile_name=profile)
67
- if region is not None:
68
- return session.client(service_name, region_name=region, config=config)
69
- else:
70
- return session.client(service_name, config=config)
71
- else:
72
- if region is not None:
73
- return boto3.client(service_name, region_name=region, config=config)
81
+ # Check if client is already in cache
82
+ if cache_key in cls._client_cache:
83
+ logger.info(f'Using cached boto3 client for {service_name}')
84
+ return cls._client_cache[cache_key]
85
+
86
+ # Create config with user agent suffix
87
+ config = Config(user_agent_extra=f'awslabs/mcp/eks-mcp-server/{__version__}')
88
+
89
+ # Create session with profile if specified
90
+ if profile:
91
+ session = boto3.Session(profile_name=profile)
92
+ if region is not None:
93
+ client = session.client(service_name, region_name=region, config=config)
94
+ else:
95
+ client = session.client(service_name, config=config)
74
96
  else:
75
- return boto3.client(service_name, config=config)
97
+ if region is not None:
98
+ client = boto3.client(service_name, region_name=region, config=config)
99
+ else:
100
+ client = boto3.client(service_name, config=config)
101
+
102
+ # Cache the client
103
+ cls._client_cache[cache_key] = client
104
+
105
+ return client
106
+ except Exception as e:
107
+ # Re-raise with more context
108
+ raise Exception(f'Failed to create boto3 client for {service_name}: {str(e)}')
@@ -44,7 +44,6 @@ class IAMHandler:
44
44
  allow_write: Whether to enable write access (default: False)
45
45
  """
46
46
  self.mcp = mcp
47
- self.iam_client = AwsHelper.create_boto3_client('iam')
48
47
  self.allow_write = allow_write
49
48
 
50
49
  # Register tools
@@ -94,15 +93,18 @@ class IAMHandler:
94
93
  try:
95
94
  log_with_request_id(ctx, LogLevel.INFO, f'Describing IAM role: {role_name}')
96
95
 
96
+ # Get IAM client
97
+ iam_client = AwsHelper.create_boto3_client('iam')
98
+
97
99
  # Get role details
98
- role_response = self.iam_client.get_role(RoleName=role_name)
100
+ role_response = iam_client.get_role(RoleName=role_name)
99
101
  role = role_response['Role']
100
102
 
101
103
  # Get attached managed policies
102
- managed_policies = self._get_managed_policies(ctx, role_name)
104
+ managed_policies = self._get_managed_policies(ctx, iam_client, role_name)
103
105
 
104
106
  # Get inline policies
105
- inline_policies = self._get_inline_policies(ctx, role_name)
107
+ inline_policies = self._get_inline_policies(ctx, iam_client, role_name)
106
108
 
107
109
  # Parse the assume role policy document if it's a string, otherwise use it directly
108
110
  if isinstance(role['AssumeRolePolicyDocument'], str):
@@ -210,8 +212,11 @@ class IAMHandler:
210
212
  permissions_added={},
211
213
  )
212
214
 
215
+ # Get IAM client
216
+ iam_client = AwsHelper.create_boto3_client('iam')
217
+
213
218
  # Create the inline policy
214
- return self._create_inline_policy(ctx, role_name, policy_name, permissions)
219
+ return self._create_inline_policy(ctx, iam_client, role_name, policy_name, permissions)
215
220
 
216
221
  except Exception as e:
217
222
  error_message = f'Failed to create inline policy: {str(e)}'
@@ -226,27 +231,28 @@ class IAMHandler:
226
231
  permissions_added={},
227
232
  )
228
233
 
229
- def _get_managed_policies(self, ctx, role_name):
234
+ def _get_managed_policies(self, ctx, iam_client, role_name):
230
235
  """Get managed policies attached to a role.
231
236
 
232
237
  Args:
233
238
  ctx: The MCP context
239
+ iam_client: IAM client to use
234
240
  role_name: Name of the IAM role
235
241
 
236
242
  Returns:
237
243
  List of PolicySummary objects
238
244
  """
239
245
  managed_policies = []
240
- managed_policies_response = self.iam_client.list_attached_role_policies(RoleName=role_name)
246
+ managed_policies_response = iam_client.list_attached_role_policies(RoleName=role_name)
241
247
 
242
248
  for policy in managed_policies_response.get('AttachedPolicies', []):
243
249
  policy_arn = policy['PolicyArn']
244
- policy_details = self.iam_client.get_policy(PolicyArn=policy_arn)['Policy']
250
+ policy_details = iam_client.get_policy(PolicyArn=policy_arn)['Policy']
245
251
 
246
252
  # Get the policy version details to get the policy document
247
253
  policy_version = None
248
254
  try:
249
- policy_version_response = self.iam_client.get_policy_version(
255
+ policy_version_response = iam_client.get_policy_version(
250
256
  PolicyArn=policy_arn, VersionId=policy_details.get('DefaultVersionId', 'v1')
251
257
  )
252
258
  policy_version = policy_version_response.get('PolicyVersion', {})
@@ -265,21 +271,22 @@ class IAMHandler:
265
271
 
266
272
  return managed_policies
267
273
 
268
- def _get_inline_policies(self, ctx, role_name):
274
+ def _get_inline_policies(self, ctx, iam_client, role_name):
269
275
  """Get inline policies embedded in a role.
270
276
 
271
277
  Args:
272
278
  ctx: The MCP context
279
+ iam_client: IAM client to use
273
280
  role_name: Name of the IAM role
274
281
 
275
282
  Returns:
276
283
  List of PolicySummary objects
277
284
  """
278
285
  inline_policies = []
279
- inline_policies_response = self.iam_client.list_role_policies(RoleName=role_name)
286
+ inline_policies_response = iam_client.list_role_policies(RoleName=role_name)
280
287
 
281
288
  for policy_name in inline_policies_response.get('PolicyNames', []):
282
- policy_response = self.iam_client.get_role_policy(
289
+ policy_response = iam_client.get_role_policy(
283
290
  RoleName=role_name, PolicyName=policy_name
284
291
  )
285
292
 
@@ -293,11 +300,12 @@ class IAMHandler:
293
300
 
294
301
  return inline_policies
295
302
 
296
- def _create_inline_policy(self, ctx, role_name, policy_name, permissions):
303
+ def _create_inline_policy(self, ctx, iam_client, role_name, policy_name, permissions):
297
304
  """Create a new inline policy with the specified permissions.
298
305
 
299
306
  Args:
300
307
  ctx: The MCP context
308
+ iam_client: IAM client to use
301
309
  role_name: Name of the role
302
310
  policy_name: Name of the new policy to create
303
311
  permissions: Permissions to include in the policy
@@ -313,7 +321,7 @@ class IAMHandler:
313
321
 
314
322
  # Check if the policy already exists
315
323
  try:
316
- self.iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
324
+ iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
317
325
  # If we get here, the policy exists
318
326
  error_message = f'Policy {policy_name} already exists in role {role_name}. Cannot modify existing policies.'
319
327
  log_with_request_id(ctx, LogLevel.ERROR, error_message)
@@ -324,7 +332,7 @@ class IAMHandler:
324
332
  role_name=role_name,
325
333
  permissions_added={},
326
334
  )
327
- except self.iam_client.exceptions.NoSuchEntityException:
335
+ except iam_client.exceptions.NoSuchEntityException:
328
336
  # Policy doesn't exist, we can create it
329
337
  pass
330
338
 
@@ -335,7 +343,7 @@ class IAMHandler:
335
343
  self._add_permissions_to_document(policy_document, permissions)
336
344
 
337
345
  # Create the policy
338
- self.iam_client.put_role_policy(
346
+ iam_client.put_role_policy(
339
347
  RoleName=role_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
340
348
  )
341
349
 
@@ -55,24 +55,17 @@ class K8sClientCache:
55
55
  # Client cache with TTL to handle token expiration
56
56
  self._client_cache = TTLCache(maxsize=100, ttl=TOKEN_TTL)
57
57
 
58
- # Clients for credential retrieval
59
- self._eks_client = None
60
- self._sts_client = None
58
+ # Flag to track if STS event handlers have been registered
59
+ self._sts_event_handlers_registered = False
61
60
 
62
61
  self._initialized = True
63
62
 
64
- def _get_eks_client(self):
65
- """Get or create the EKS client."""
66
- if self._eks_client is None:
67
- self._eks_client = AwsHelper.create_boto3_client('eks')
68
- return self._eks_client
69
-
70
63
  def _get_sts_client(self):
71
- """Get or create the STS client with event handlers registered."""
72
- if self._sts_client is None:
73
- sts_client = AwsHelper.create_boto3_client('sts')
64
+ """Get the STS client with event handlers registered."""
65
+ sts_client = AwsHelper.create_boto3_client('sts')
74
66
 
75
- # Register STS event handlers
67
+ # Register STS event handlers only once
68
+ if not self._sts_event_handlers_registered:
76
69
  sts_client.meta.events.register(
77
70
  'provide-client-params.sts.GetCallerIdentity',
78
71
  self._retrieve_k8s_aws_id,
@@ -81,10 +74,9 @@ class K8sClientCache:
81
74
  'before-sign.sts.GetCallerIdentity',
82
75
  self._inject_k8s_aws_id_header,
83
76
  )
77
+ self._sts_event_handlers_registered = True
84
78
 
85
- self._sts_client = sts_client
86
-
87
- return self._sts_client
79
+ return sts_client
88
80
 
89
81
  def _retrieve_k8s_aws_id(self, params, context, **kwargs):
90
82
  """Retrieve the Kubernetes AWS ID from parameters."""
@@ -109,7 +101,7 @@ class K8sClientCache:
109
101
  ValueError: If the cluster credentials are invalid
110
102
  Exception: If there's an error getting the cluster credentials
111
103
  """
112
- eks_client = self._get_eks_client()
104
+ eks_client = AwsHelper.create_boto3_client('eks')
113
105
  sts_client = self._get_sts_client()
114
106
 
115
107
  # Get cluster details
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.eks-mcp-server
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for EKS
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/eks-mcp-server/
@@ -128,7 +128,7 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
128
128
 
129
129
  1. Open Cursor.
130
130
  2. Click the gear icon (⚙️) in the top right to open the settings panel, click **MCP**, **Add new global MCP server**.
131
- 3. Paste your MCP server definition. For example, this example shows how to configure the EKS MCP Server, including enabling mutating actions by adding the `--allow-write` flag to the server arguments:
131
+ 3. Paste your MCP server definition. For example, this example shows how to configure the EKS MCP Server, including enabling mutating actions with the `--allow-write` flag and access to sensitive data with the `--allow-sensitive-data-access` flag (see the Arguments section for more details):
132
132
 
133
133
  **For Mac/Linux:**
134
134
 
@@ -141,7 +141,8 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
141
141
  "command": "uvx",
142
142
  "args": [
143
143
  "awslabs.eks-mcp-server@latest",
144
- "--allow-write"
144
+ "--allow-write",
145
+ "--allow-sensitive-data-access"
145
146
  ],
146
147
  "env": {
147
148
  "FASTMCP_LOG_LEVEL": "ERROR"
@@ -165,7 +166,8 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
165
166
  "--from",
166
167
  "awslabs.eks-mcp-server@latest",
167
168
  "awslabs.eks-mcp-server.exe",
168
- "--allow-write"
169
+ "--allow-write",
170
+ "--allow-sensitive-data-access"
169
171
  ],
170
172
  "env": {
171
173
  "FASTMCP_LOG_LEVEL": "ERROR"
@@ -183,7 +185,9 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
183
185
  **Set up the Amazon Q Developer CLI**
184
186
 
185
187
  1. Install the [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-installing.html) .
186
- 2. The Q Developer CLI supports MCP servers for tools and prompts out-of-the-box. Edit your Q developer CLI's MCP configuration file named mcp.json following [these instructions](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html). For example:
188
+ 2. The Q Developer CLI supports MCP servers for tools and prompts out-of-the-box. Edit your Q developer CLI's MCP configuration file named mcp.json following [these instructions](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html).
189
+
190
+ The example below includes both the `--allow-write` flag for mutating operations and the `--allow-sensitive-data-access` flag for accessing logs and events (see the Arguments section for more details):
187
191
 
188
192
  **For Mac/Linux:**
189
193
 
@@ -192,7 +196,11 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
192
196
  "mcpServers": {
193
197
  "awslabs.eks-mcp-server": {
194
198
  "command": "uvx",
195
- "args": ["awslabs.eks-mcp-server@latest"],
199
+ "args": [
200
+ "awslabs.eks-mcp-server@latest",
201
+ "--allow-write",
202
+ "--allow-sensitive-data-access"
203
+ ],
196
204
  "env": {
197
205
  "FASTMCP_LOG_LEVEL": "ERROR"
198
206
  },
@@ -210,7 +218,13 @@ This quickstart guide walks you through the steps to configure the Amazon EKS MC
210
218
  "mcpServers": {
211
219
  "awslabs.eks-mcp-server": {
212
220
  "command": "uvx",
213
- "args": ["--from", "awslabs.eks-mcp-server@latest", "awslabs.eks-mcp-server.exe"],
221
+ "args": [
222
+ "--from",
223
+ "awslabs.eks-mcp-server@latest",
224
+ "awslabs.eks-mcp-server.exe",
225
+ "--allow-write",
226
+ "--allow-sensitive-data-access"
227
+ ],
214
228
  "env": {
215
229
  "FASTMCP_LOG_LEVEL": "ERROR"
216
230
  },
@@ -294,7 +308,7 @@ Enables write access mode, which allows mutating operations (e.g., create, updat
294
308
 
295
309
  #### `--allow-sensitive-data-access` (optional)
296
310
 
297
- Enables access to sensitive data such as logs, events, and Kubernetes Secrets.
311
+ Enables access to sensitive data such as logs, events, and Kubernetes Secrets. This flag is required for tools that access potentially sensitive information, such as get_pod_logs, get_k8s_events, get_cloudwatch_logs, and manage_k8s_resource (when used to read Kubernetes secrets).
298
312
 
299
313
  * Default: false (Access to sensitive data is restricted by default)
300
314
  * Example: Add `--allow-sensitive-data-access` to the `args` list in your MCP server definition.
@@ -1,14 +1,14 @@
1
1
  awslabs/__init__.py,sha256=WuqxdDgUZylWNmVoPKiK7qGsTB_G4UmuXIrJ-VBwDew,731
2
2
  awslabs/eks_mcp_server/__init__.py,sha256=ghJqbcPKp3-jM8LmEQzU_KRSbVm6yKQFg7C0ZwxAdeA,668
3
- awslabs/eks_mcp_server/aws_helper.py,sha256=uiH-CSJPo_L903EzmV8HZB6xg_F4Jm_-BmJk_3AAH4g,2850
3
+ awslabs/eks_mcp_server/aws_helper.py,sha256=Is0BCVPjhO-AqKFF0MnGpzNRjAe8E896VGrKWCz4gfo,4031
4
4
  awslabs/eks_mcp_server/cloudwatch_handler.py,sha256=k3GsORIFswOknxYM0reCBsCHXn--gSwl7WVtxkUTyzg,28853
5
5
  awslabs/eks_mcp_server/cloudwatch_metrics_guidance_handler.py,sha256=b0fFMvsGX98HKK4kVFI1YbhZqZC623lZ6TNXs3EiRSI,5283
6
6
  awslabs/eks_mcp_server/consts.py,sha256=XBat-KcMckueQQpDeLkD_Nv_9G9kX0_d48sMEHtZ5HQ,1380
7
7
  awslabs/eks_mcp_server/eks_kb_handler.py,sha256=6L3wS500AadKburhwf0zXEapXscd4VZCTY8t61u-j1Y,3548
8
8
  awslabs/eks_mcp_server/eks_stack_handler.py,sha256=gbZizqaumsAz5x-fLM-VJlmUCzTp7TwtbOGn8u6_omc,29721
9
- awslabs/eks_mcp_server/iam_handler.py,sha256=t2QIJVP61lAVTiTB9VCko9RDXjAtz8nuXr_MRdKG9pw,14586
9
+ awslabs/eks_mcp_server/iam_handler.py,sha256=2mmyPyPLoIgIhA9y1QlpK3BamI3d0Xg6arKrWZutAvE,14860
10
10
  awslabs/eks_mcp_server/k8s_apis.py,sha256=YRx29w-7n3LX3DsniPgULuxNx_6Mkw-Jzh-PGetZDag,20448
11
- awslabs/eks_mcp_server/k8s_client_cache.py,sha256=Nh8V6IXvWltQwiBhFk8KgDUggmf9dqQ-sR3kENBnecM,5871
11
+ awslabs/eks_mcp_server/k8s_client_cache.py,sha256=vm-8VKC3zaCqpW9pOUmRDFulxzYCX8p8OvyOgtvh96o,5697
12
12
  awslabs/eks_mcp_server/k8s_handler.py,sha256=vA08ONRDky7S5hINif8hxe_Y1KAArQDzOao1YLf4Uck,49447
13
13
  awslabs/eks_mcp_server/logging_helper.py,sha256=hr8xZhAZOKyR7dkwc7bhqkDVuSDI3BRK3-UzllQkWgE,1854
14
14
  awslabs/eks_mcp_server/models.py,sha256=UtMmZaRJNjV8I9A8jCD9vzU66tXYOLna8RzaMNQPddE,11930
@@ -18,9 +18,9 @@ awslabs/eks_mcp_server/scripts/update_eks_cloudwatch_metrics_guidance.py,sha256=
18
18
  awslabs/eks_mcp_server/templates/eks-templates/eks-with-vpc.yaml,sha256=_Lxk2MEXNA7N0-kvXckxwBamDEagjGvC6-Z5uxhVO5s,10774
19
19
  awslabs/eks_mcp_server/templates/k8s-templates/deployment.yaml,sha256=J2efYFISlT3sTvf8_BJV3p0_m51cltqiRhXdBXb9YJs,2343
20
20
  awslabs/eks_mcp_server/templates/k8s-templates/service.yaml,sha256=DA0Db_5yjUZmnnYy5Bljcv3hj7D6YvFFWFRB6GiIstY,414
21
- awslabs_eks_mcp_server-0.1.4.dist-info/METADATA,sha256=E8OHB-NvW_u6p-z4eHytjP5k3fBlPv0HZGIhdp3_1mk,29163
22
- awslabs_eks_mcp_server-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- awslabs_eks_mcp_server-0.1.4.dist-info/entry_points.txt,sha256=VydotfOJYck8o4TPsaF6Pjmc8Bp_doacYXSE_71qH4c,78
24
- awslabs_eks_mcp_server-0.1.4.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
25
- awslabs_eks_mcp_server-0.1.4.dist-info/licenses/NOTICE,sha256=gnCtD34qTDnb2Lykm9kNFYkqZIvqJHGuq1ZJBkl6EgE,90
26
- awslabs_eks_mcp_server-0.1.4.dist-info/RECORD,,
21
+ awslabs_eks_mcp_server-0.1.5.dist-info/METADATA,sha256=jRaI-c8WKjao0_9Kc5VPposShIpBMxtOuS6XJrOqOnA,29920
22
+ awslabs_eks_mcp_server-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ awslabs_eks_mcp_server-0.1.5.dist-info/entry_points.txt,sha256=VydotfOJYck8o4TPsaF6Pjmc8Bp_doacYXSE_71qH4c,78
24
+ awslabs_eks_mcp_server-0.1.5.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
25
+ awslabs_eks_mcp_server-0.1.5.dist-info/licenses/NOTICE,sha256=gnCtD34qTDnb2Lykm9kNFYkqZIvqJHGuq1ZJBkl6EgE,90
26
+ awslabs_eks_mcp_server-0.1.5.dist-info/RECORD,,