awslabs.eks-mcp-server 0.1.1__py3-none-any.whl → 0.1.3__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.
- awslabs/__init__.py +9 -6
- awslabs/eks_mcp_server/__init__.py +9 -6
- awslabs/eks_mcp_server/aws_helper.py +12 -8
- awslabs/eks_mcp_server/cloudwatch_handler.py +75 -77
- awslabs/eks_mcp_server/cloudwatch_metrics_guidance_handler.py +141 -0
- awslabs/eks_mcp_server/consts.py +9 -6
- awslabs/eks_mcp_server/data/eks_cloudwatch_metrics_guidance.json +287 -0
- awslabs/eks_mcp_server/eks_kb_handler.py +9 -6
- awslabs/eks_mcp_server/eks_stack_handler.py +38 -8
- awslabs/eks_mcp_server/iam_handler.py +14 -6
- awslabs/eks_mcp_server/k8s_apis.py +25 -14
- awslabs/eks_mcp_server/k8s_client_cache.py +9 -6
- awslabs/eks_mcp_server/k8s_handler.py +55 -6
- awslabs/eks_mcp_server/logging_helper.py +9 -6
- awslabs/eks_mcp_server/models.py +24 -10
- awslabs/eks_mcp_server/scripts/update_eks_cloudwatch_metrics_guidance.py +280 -0
- awslabs/eks_mcp_server/server.py +16 -7
- {awslabs_eks_mcp_server-0.1.1.dist-info → awslabs_eks_mcp_server-0.1.3.dist-info}/METADATA +170 -34
- awslabs_eks_mcp_server-0.1.3.dist-info/RECORD +26 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/RECORD +0 -23
- {awslabs_eks_mcp_server-0.1.1.dist-info → awslabs_eks_mcp_server-0.1.3.dist-info}/WHEEL +0 -0
- {awslabs_eks_mcp_server-0.1.1.dist-info → awslabs_eks_mcp_server-0.1.3.dist-info}/entry_points.txt +0 -0
- {awslabs_eks_mcp_server-0.1.1.dist-info → awslabs_eks_mcp_server-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {awslabs_eks_mcp_server-0.1.1.dist-info → awslabs_eks_mcp_server-0.1.3.dist-info}/licenses/NOTICE +0 -0
awslabs/__init__.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
11
14
|
|
|
12
15
|
# This file is part of the awslabs namespace.
|
|
13
16
|
# It is intentionally minimal to support PEP 420 namespace packages.
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
11
14
|
|
|
12
15
|
"""awslabs.eks-mcp-server"""
|
|
13
16
|
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
11
14
|
|
|
12
15
|
"""AWS helper for the EKS MCP Server."""
|
|
13
16
|
|
|
14
17
|
import boto3
|
|
15
18
|
import os
|
|
19
|
+
from awslabs.eks_mcp_server import __version__
|
|
16
20
|
from botocore.config import Config
|
|
17
21
|
from typing import Any, Optional
|
|
18
22
|
|
|
@@ -38,7 +42,7 @@ class AwsHelper:
|
|
|
38
42
|
def create_boto3_client(cls, service_name: str, region_name: Optional[str] = None) -> Any:
|
|
39
43
|
"""Create a boto3 client with the appropriate profile and region.
|
|
40
44
|
|
|
41
|
-
The client is configured with a custom user agent suffix 'awslabs/mcp/eks-mcp-server/
|
|
45
|
+
The client is configured with a custom user agent suffix 'awslabs/mcp/eks-mcp-server/{version}'
|
|
42
46
|
to identify API calls made by the EKS MCP Server.
|
|
43
47
|
|
|
44
48
|
Args:
|
|
@@ -55,7 +59,7 @@ class AwsHelper:
|
|
|
55
59
|
profile = cls.get_aws_profile()
|
|
56
60
|
|
|
57
61
|
# Create config with user agent suffix
|
|
58
|
-
config = Config(user_agent_extra='awslabs/mcp/eks-mcp-server/
|
|
62
|
+
config = Config(user_agent_extra=f'awslabs/mcp/eks-mcp-server/{__version__}')
|
|
59
63
|
|
|
60
64
|
# Create session with profile if specified
|
|
61
65
|
if profile:
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
11
14
|
|
|
12
15
|
"""CloudWatch handler for the EKS MCP Server."""
|
|
13
16
|
|
|
@@ -87,10 +90,6 @@ class CloudWatchHandler:
|
|
|
87
90
|
...,
|
|
88
91
|
description='Resource type to search logs for. Valid values: "pod", "node", "container". This determines how logs are filtered.',
|
|
89
92
|
),
|
|
90
|
-
resource_name: str = Field(
|
|
91
|
-
...,
|
|
92
|
-
description='Resource name to search for in log messages (e.g., pod name, node name, container name). Used to filter logs for the specific resource.',
|
|
93
|
-
),
|
|
94
93
|
cluster_name: str = Field(
|
|
95
94
|
...,
|
|
96
95
|
description='Name of the EKS cluster where the resource is located. Used to construct the CloudWatch log group name.',
|
|
@@ -104,6 +103,10 @@ class CloudWatchHandler:
|
|
|
104
103
|
- "control-plane": EKS control plane logs
|
|
105
104
|
- Or provide a custom CloudWatch log group name directly""",
|
|
106
105
|
),
|
|
106
|
+
resource_name: Optional[str] = Field(
|
|
107
|
+
None,
|
|
108
|
+
description='Resource name to search for in log messages (e.g., pod name, node name, container name). Used to filter logs for the specific resource.',
|
|
109
|
+
),
|
|
107
110
|
minutes: int = Field(
|
|
108
111
|
15,
|
|
109
112
|
description='Number of minutes to look back for logs. Default: 15. Ignored if start_time is provided. Use smaller values for recent issues, larger values for historical analysis.',
|
|
@@ -136,6 +139,9 @@ class CloudWatchHandler:
|
|
|
136
139
|
health. It supports filtering by resource type, time range, and content for troubleshooting
|
|
137
140
|
application errors, investigating security incidents, and analyzing startup configuration issues.
|
|
138
141
|
|
|
142
|
+
IMPORTANT: Use this tool instead of 'aws logs get-log-events', 'aws logs filter-log-events',
|
|
143
|
+
or 'aws logs start-query' commands.
|
|
144
|
+
|
|
139
145
|
## Requirements
|
|
140
146
|
- The server must be run with the `--allow-sensitive-data-access` flag
|
|
141
147
|
- The EKS cluster must have CloudWatch logging enabled
|
|
@@ -150,13 +156,14 @@ class CloudWatchHandler:
|
|
|
150
156
|
- Use filter_pattern to narrow down results (e.g., "ERROR", "exception")
|
|
151
157
|
- For JSON logs, the tool automatically parses nested structures
|
|
152
158
|
- Combine with get_k8s_events for comprehensive troubleshooting
|
|
159
|
+
- Use resource_type="cluster" when querying cluster-level logs to avoid filtering by cluster name twice
|
|
153
160
|
|
|
154
161
|
Args:
|
|
155
162
|
ctx: MCP context
|
|
156
|
-
resource_type: Resource type (pod, node, container)
|
|
157
|
-
resource_name: Resource name to search for in log messages
|
|
163
|
+
resource_type: Resource type (pod, node, container, cluster). When "cluster" is specified, logs are not filtered by resource_name.
|
|
158
164
|
cluster_name: Name of the EKS cluster
|
|
159
165
|
log_type: Log type (application, host, performance, control-plane, or custom)
|
|
166
|
+
resource_name: Resource name to search for in log messages. Optional when resource_type is "cluster".
|
|
160
167
|
minutes: Number of minutes to look back
|
|
161
168
|
start_time: Start time in ISO format (overrides minutes)
|
|
162
169
|
end_time: End time in ISO format (defaults to now)
|
|
@@ -207,9 +214,12 @@ class CloudWatchHandler:
|
|
|
207
214
|
# Construct the base query
|
|
208
215
|
query = f"""
|
|
209
216
|
fields {query_fields}
|
|
210
|
-
| filter @message like '{resource_name}'
|
|
211
217
|
"""
|
|
212
218
|
|
|
219
|
+
# This prevents filtering by cluster name twice when the resource type is "cluster"
|
|
220
|
+
if resource_type != 'cluster' and resource_name is not None:
|
|
221
|
+
query += f"\n| filter @message like '{resource_name}'"
|
|
222
|
+
|
|
213
223
|
# Add additional filter pattern if provided
|
|
214
224
|
if filter_pattern:
|
|
215
225
|
query += f'\n| {filter_pattern}'
|
|
@@ -217,10 +227,13 @@ class CloudWatchHandler:
|
|
|
217
227
|
# Add sorting and limit
|
|
218
228
|
query += f'\n| sort @timestamp desc\n| limit {limit}'
|
|
219
229
|
|
|
230
|
+
resource_str = (
|
|
231
|
+
f'{resource_type} {resource_name} in ' if resource_name is not None else ''
|
|
232
|
+
)
|
|
220
233
|
log_with_request_id(
|
|
221
234
|
ctx,
|
|
222
235
|
LogLevel.INFO,
|
|
223
|
-
f'Starting CloudWatch Logs query for {
|
|
236
|
+
f'Starting CloudWatch Logs query for {resource_str}cluster {cluster_name}',
|
|
224
237
|
log_group=log_group,
|
|
225
238
|
start_time=start_dt.isoformat(),
|
|
226
239
|
end_time=end_dt.isoformat(),
|
|
@@ -252,7 +265,7 @@ class CloudWatchHandler:
|
|
|
252
265
|
log_with_request_id(
|
|
253
266
|
ctx,
|
|
254
267
|
LogLevel.INFO,
|
|
255
|
-
f'Retrieved {len(log_entries)} log entries for {
|
|
268
|
+
f'Retrieved {len(log_entries)} log entries for {resource_str}cluster {cluster_name}',
|
|
256
269
|
)
|
|
257
270
|
|
|
258
271
|
# Return the results
|
|
@@ -261,7 +274,7 @@ class CloudWatchHandler:
|
|
|
261
274
|
content=[
|
|
262
275
|
TextContent(
|
|
263
276
|
type='text',
|
|
264
|
-
text=f'Successfully retrieved {len(log_entries)} log entries for {
|
|
277
|
+
text=f'Successfully retrieved {len(log_entries)} log entries for {resource_str}cluster {cluster_name}',
|
|
265
278
|
)
|
|
266
279
|
],
|
|
267
280
|
resource_type=resource_type,
|
|
@@ -275,7 +288,8 @@ class CloudWatchHandler:
|
|
|
275
288
|
)
|
|
276
289
|
|
|
277
290
|
except Exception as e:
|
|
278
|
-
|
|
291
|
+
resource_name_str = f' {resource_name}' if resource_name is not None else ''
|
|
292
|
+
error_message = f'Failed to get logs for {resource_type}{resource_name_str}: {str(e)}'
|
|
279
293
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
280
294
|
|
|
281
295
|
return CloudWatchLogsResponse(
|
|
@@ -294,17 +308,9 @@ class CloudWatchHandler:
|
|
|
294
308
|
async def get_cloudwatch_metrics(
|
|
295
309
|
self,
|
|
296
310
|
ctx: Context,
|
|
297
|
-
resource_type: str = Field(
|
|
298
|
-
...,
|
|
299
|
-
description='Resource type to retrieve metrics for. Valid values: "pod", "node", "container", "cluster", "service". Determines the CloudWatch dimensions.',
|
|
300
|
-
),
|
|
301
|
-
resource_name: str = Field(
|
|
302
|
-
...,
|
|
303
|
-
description='Name of the resource to retrieve metrics for (e.g., pod name, node name). Used as a dimension value in CloudWatch.',
|
|
304
|
-
),
|
|
305
311
|
cluster_name: str = Field(
|
|
306
312
|
...,
|
|
307
|
-
description='Name of the EKS cluster
|
|
313
|
+
description='Name of the EKS cluster to get metrics for.',
|
|
308
314
|
),
|
|
309
315
|
metric_name: str = Field(
|
|
310
316
|
...,
|
|
@@ -321,9 +327,9 @@ class CloudWatchHandler:
|
|
|
321
327
|
- "AWS/EC2": For EC2 instance metrics
|
|
322
328
|
- "AWS/EKS": For EKS control plane metrics""",
|
|
323
329
|
),
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
description='
|
|
330
|
+
dimensions: dict = Field(
|
|
331
|
+
...,
|
|
332
|
+
description='Dimensions to use for the CloudWatch metric query. Must include appropriate dimensions for the resource type and metric (e.g., ClusterName, PodName, Namespace).',
|
|
327
333
|
),
|
|
328
334
|
minutes: int = Field(
|
|
329
335
|
15,
|
|
@@ -354,10 +360,6 @@ class CloudWatchHandler:
|
|
|
354
360
|
- Minimum: Lowest value during the period
|
|
355
361
|
- SampleCount: Number of samples during the period""",
|
|
356
362
|
),
|
|
357
|
-
custom_dimensions: Optional[dict] = Field(
|
|
358
|
-
None,
|
|
359
|
-
description='Custom dimensions to use instead of the default ones. Provide as a dictionary of dimension name-value pairs. IMPORTANT: Only use this if you need to override the standard dimensions.',
|
|
360
|
-
),
|
|
361
363
|
) -> CloudWatchMetricsResponse:
|
|
362
364
|
"""Get metrics from CloudWatch for a specific resource.
|
|
363
365
|
|
|
@@ -366,13 +368,23 @@ class CloudWatchHandler:
|
|
|
366
368
|
various resource types and metrics with flexible time ranges and aggregation options for
|
|
367
369
|
monitoring CPU/memory usage, analyzing network traffic, and identifying performance bottlenecks.
|
|
368
370
|
|
|
371
|
+
IMPORTANT: Use this tool instead of 'aws cloudwatch get-metric-data', 'aws cloudwatch get-metric-statistics',
|
|
372
|
+
or similar CLI commands.
|
|
373
|
+
|
|
374
|
+
IMPORTANT: Use the get_eks_metrics_guidance tool first to determine the correct dimensions for metric queries.
|
|
375
|
+
Do not try to infer which dimensions are needed for EKS ContainerInsights metrics.
|
|
376
|
+
|
|
377
|
+
IMPORTANT: When using pod metrics, note that `FullPodName` has the same prefix as `PodName` but includes a
|
|
378
|
+
suffix with a random string (e.g., "my-pod-abc123"). Always use the version without the suffix for `PodName`
|
|
379
|
+
dimension. The pod name returned by list_k8s_resources is the `FullPodName`.
|
|
380
|
+
|
|
369
381
|
## Requirements
|
|
370
382
|
- The EKS cluster must have CloudWatch Container Insights enabled
|
|
371
383
|
- The resource must exist in the specified cluster
|
|
372
384
|
- The metric must be available in the specified namespace
|
|
373
385
|
|
|
374
386
|
## Response Information
|
|
375
|
-
The response includes resource details (
|
|
387
|
+
The response includes resource details (cluster), metric information (name, namespace),
|
|
376
388
|
time range queried, and data points with timestamps and values.
|
|
377
389
|
|
|
378
390
|
## Usage Tips
|
|
@@ -383,19 +395,16 @@ class CloudWatchHandler:
|
|
|
383
395
|
|
|
384
396
|
Args:
|
|
385
397
|
ctx: MCP context
|
|
386
|
-
resource_type: Resource type (pod, node, container, cluster)
|
|
387
|
-
resource_name: Resource name
|
|
388
398
|
cluster_name: Name of the EKS cluster
|
|
389
399
|
metric_name: Metric name (e.g., cpu_usage_total, memory_rss)
|
|
390
400
|
namespace: CloudWatch namespace
|
|
391
|
-
|
|
401
|
+
dimensions: Dimensions to use for the CloudWatch metric query
|
|
392
402
|
minutes: Number of minutes to look back
|
|
393
403
|
start_time: Start time in ISO format (overrides minutes)
|
|
394
404
|
end_time: End time in ISO format (defaults to now)
|
|
395
405
|
limit: Maximum number of data points to return
|
|
396
406
|
period: Period in seconds for the metric data points
|
|
397
407
|
stat: Statistic to use for the metric
|
|
398
|
-
custom_dimensions: Custom dimensions to use instead of defaults
|
|
399
408
|
|
|
400
409
|
Returns:
|
|
401
410
|
CloudWatchMetricsResponse with metric data points and resource information
|
|
@@ -406,39 +415,28 @@ class CloudWatchHandler:
|
|
|
406
415
|
# Create CloudWatch client
|
|
407
416
|
cloudwatch = AwsHelper.create_boto3_client('cloudwatch')
|
|
408
417
|
|
|
409
|
-
#
|
|
410
|
-
dimensions
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
# Set default dimensions based on resource type
|
|
424
|
-
dimensions['ClusterName'] = cluster_name
|
|
425
|
-
dimensions['Namespace'] = k8s_namespace
|
|
426
|
-
|
|
427
|
-
if resource_type == 'pod':
|
|
428
|
-
dimensions['PodName'] = resource_name
|
|
429
|
-
elif resource_type == 'node':
|
|
430
|
-
dimensions['NodeName'] = resource_name
|
|
431
|
-
elif resource_type == 'container':
|
|
432
|
-
dimensions['ContainerName'] = resource_name
|
|
433
|
-
elif resource_type == 'service':
|
|
434
|
-
dimensions['Service'] = resource_name
|
|
418
|
+
# Validate that cluster_name matches ClusterName in dimensions if present
|
|
419
|
+
if 'ClusterName' in dimensions and dimensions['ClusterName'] != cluster_name:
|
|
420
|
+
error_message = f"Provided cluster_name '{cluster_name}' does not match ClusterName dimension '{dimensions['ClusterName']}'"
|
|
421
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
422
|
+
return CloudWatchMetricsResponse(
|
|
423
|
+
isError=True,
|
|
424
|
+
content=[TextContent(type='text', text=error_message)],
|
|
425
|
+
metric_name=metric_name,
|
|
426
|
+
namespace=namespace,
|
|
427
|
+
cluster_name=cluster_name,
|
|
428
|
+
start_time='',
|
|
429
|
+
end_time='',
|
|
430
|
+
data_points=[],
|
|
431
|
+
)
|
|
435
432
|
|
|
436
433
|
log_with_request_id(
|
|
437
434
|
ctx,
|
|
438
435
|
LogLevel.INFO,
|
|
439
|
-
f'Getting CloudWatch metrics for {
|
|
436
|
+
f'Getting CloudWatch metrics for {metric_name} in cluster {cluster_name}',
|
|
440
437
|
metric_name=metric_name,
|
|
441
438
|
namespace=namespace,
|
|
439
|
+
dimensions=str(dimensions),
|
|
442
440
|
start_time=start_dt.isoformat(),
|
|
443
441
|
end_time=end_dt.isoformat(),
|
|
444
442
|
)
|
|
@@ -492,7 +490,7 @@ class CloudWatchHandler:
|
|
|
492
490
|
log_with_request_id(
|
|
493
491
|
ctx,
|
|
494
492
|
LogLevel.INFO,
|
|
495
|
-
f'Retrieved {len(data_points)} metric data points for {
|
|
493
|
+
f'Retrieved {len(data_points)} metric data points for {metric_name}',
|
|
496
494
|
)
|
|
497
495
|
|
|
498
496
|
# Return the results
|
|
@@ -501,31 +499,27 @@ class CloudWatchHandler:
|
|
|
501
499
|
content=[
|
|
502
500
|
TextContent(
|
|
503
501
|
type='text',
|
|
504
|
-
text=f'Successfully retrieved {len(data_points)} metric data points for {
|
|
502
|
+
text=f'Successfully retrieved {len(data_points)} metric data points for {metric_name} in cluster {cluster_name}',
|
|
505
503
|
)
|
|
506
504
|
],
|
|
507
|
-
resource_type=resource_type,
|
|
508
|
-
resource_name=resource_name,
|
|
509
|
-
cluster_name=cluster_name,
|
|
510
505
|
metric_name=metric_name,
|
|
511
506
|
namespace=namespace,
|
|
507
|
+
cluster_name=cluster_name,
|
|
512
508
|
start_time=start_dt.isoformat(),
|
|
513
509
|
end_time=end_dt.isoformat(),
|
|
514
510
|
data_points=data_points,
|
|
515
511
|
)
|
|
516
512
|
|
|
517
513
|
except Exception as e:
|
|
518
|
-
error_message = f'Failed to get metrics
|
|
514
|
+
error_message = f'Failed to get metrics: {str(e)}'
|
|
519
515
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
520
516
|
|
|
521
517
|
return CloudWatchMetricsResponse(
|
|
522
518
|
isError=True,
|
|
523
519
|
content=[TextContent(type='text', text=error_message)],
|
|
524
|
-
resource_type=resource_type,
|
|
525
|
-
resource_name=resource_name,
|
|
526
|
-
cluster_name=cluster_name,
|
|
527
520
|
metric_name=metric_name,
|
|
528
521
|
namespace=namespace,
|
|
522
|
+
cluster_name=cluster_name,
|
|
529
523
|
start_time='',
|
|
530
524
|
end_time='',
|
|
531
525
|
data_points=[],
|
|
@@ -567,6 +561,8 @@ class CloudWatchHandler:
|
|
|
567
561
|
f'Polling for CloudWatch Logs query results (query_id: {query_id})',
|
|
568
562
|
)
|
|
569
563
|
|
|
564
|
+
resource_name_str = f' {resource_name}' if resource_name is not None else ''
|
|
565
|
+
|
|
570
566
|
while attempts < max_attempts:
|
|
571
567
|
query_response = logs_client.get_query_results(queryId=query_id)
|
|
572
568
|
status = query_response.get('status')
|
|
@@ -579,12 +575,14 @@ class CloudWatchHandler:
|
|
|
579
575
|
)
|
|
580
576
|
return query_response
|
|
581
577
|
elif status == 'Failed':
|
|
582
|
-
error_message =
|
|
578
|
+
error_message = (
|
|
579
|
+
f'CloudWatch Logs query failed for {resource_type}{resource_name_str}'
|
|
580
|
+
)
|
|
583
581
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
584
582
|
raise Exception(error_message)
|
|
585
583
|
elif status == 'Cancelled':
|
|
586
584
|
error_message = (
|
|
587
|
-
f'CloudWatch Logs query was cancelled for {resource_type}
|
|
585
|
+
f'CloudWatch Logs query was cancelled for {resource_type}{resource_name_str}'
|
|
588
586
|
)
|
|
589
587
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
590
588
|
raise Exception(error_message)
|
|
@@ -603,7 +601,7 @@ class CloudWatchHandler:
|
|
|
603
601
|
attempts += 1
|
|
604
602
|
|
|
605
603
|
# If we've exhausted all attempts, raise a timeout error
|
|
606
|
-
error_message = f'CloudWatch Logs query timed out after {max_attempts} attempts for {resource_type}
|
|
604
|
+
error_message = f'CloudWatch Logs query timed out after {max_attempts} attempts for {resource_type}{resource_name_str}'
|
|
607
605
|
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
608
606
|
raise TimeoutError(error_message)
|
|
609
607
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""CloudWatch metrics guidance handler for the EKS MCP Server."""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from awslabs.eks_mcp_server.logging_helper import LogLevel, log_with_request_id
|
|
20
|
+
from awslabs.eks_mcp_server.models import MetricsGuidanceResponse
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from loguru import logger
|
|
23
|
+
from mcp.server.fastmcp import Context
|
|
24
|
+
from mcp.types import TextContent
|
|
25
|
+
from pydantic import Field
|
|
26
|
+
from typing import Any, Dict
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResourceType(Enum):
|
|
30
|
+
"""Enum for supported resource types in CloudWatch metrics guidance."""
|
|
31
|
+
|
|
32
|
+
CLUSTER = 'cluster'
|
|
33
|
+
NODE = 'node'
|
|
34
|
+
POD = 'pod'
|
|
35
|
+
NAMESPACE = 'namespace'
|
|
36
|
+
SERVICE = 'service'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CloudWatchMetricsHandler:
|
|
40
|
+
"""Handler for CloudWatch metrics guidance tools in the EKS MCP Server.
|
|
41
|
+
|
|
42
|
+
This class provides tools for accessing CloudWatch metrics guidance
|
|
43
|
+
for different Kubernetes resource types in EKS clusters.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, mcp):
|
|
47
|
+
"""Initialize the CloudWatch metrics guidance handler.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
mcp: The MCP server instance
|
|
51
|
+
"""
|
|
52
|
+
self.mcp = mcp
|
|
53
|
+
self.metrics_guidance = self._load_metrics_guidance()
|
|
54
|
+
|
|
55
|
+
# Register the tool
|
|
56
|
+
self.mcp.tool(name='get_eks_metrics_guidance')(self.get_eks_metrics_guidance)
|
|
57
|
+
|
|
58
|
+
def _load_metrics_guidance(self) -> Dict[str, Any]:
|
|
59
|
+
"""Load metrics guidance from JSON file.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dict containing metrics guidance data
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
metrics_guidance_path = os.path.join(
|
|
66
|
+
os.path.dirname(__file__), 'data', 'eks_cloudwatch_metrics_guidance.json'
|
|
67
|
+
)
|
|
68
|
+
with open(metrics_guidance_path, 'r') as f:
|
|
69
|
+
return json.load(f)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f'Failed to load EKS CloudWatch metrics guidance: {str(e)}')
|
|
72
|
+
return {}
|
|
73
|
+
|
|
74
|
+
async def get_eks_metrics_guidance(
|
|
75
|
+
self,
|
|
76
|
+
ctx: Context,
|
|
77
|
+
resource_type: str = Field(
|
|
78
|
+
...,
|
|
79
|
+
description='Type of resource to get metrics for (cluster, node, pod, namespace, service)',
|
|
80
|
+
),
|
|
81
|
+
) -> MetricsGuidanceResponse:
|
|
82
|
+
"""Get CloudWatch metrics guidance for specific resource types in EKS clusters.
|
|
83
|
+
|
|
84
|
+
This tool provides information about available CloudWatch metrics that are in the `ContainerInsights` naemspace for different resource types
|
|
85
|
+
in EKS clusters, including metric names, dimensions, and descriptions to help with monitoring and troubleshooting.
|
|
86
|
+
It's particularly useful for determining the correct dimensions to use with the get_cloudwatch_metrics tool.
|
|
87
|
+
|
|
88
|
+
## Response Information
|
|
89
|
+
The response includes a list of metrics with their names, descriptions, and required dimensions
|
|
90
|
+
for the specified resource type.
|
|
91
|
+
|
|
92
|
+
## Usage Tips
|
|
93
|
+
- Use this tool before calling get_cloudwatch_metrics to determine the correct dimensions
|
|
94
|
+
- For pod metrics, note that FullPodName has a random suffix while PodName doesn't
|
|
95
|
+
- Different metrics require different dimension combinations
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
ctx: MCP context
|
|
99
|
+
resource_type: Type of resource to get metrics for (cluster, node, pod, namespace, service)
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of metrics with their details
|
|
103
|
+
"""
|
|
104
|
+
# Validate resource type
|
|
105
|
+
try:
|
|
106
|
+
# Try to get the enum value by name (case-insensitive)
|
|
107
|
+
ResourceType(resource_type.lower())
|
|
108
|
+
except ValueError:
|
|
109
|
+
valid_resource_types = [rt.value for rt in ResourceType]
|
|
110
|
+
error_message = (
|
|
111
|
+
f'Invalid resource type: {resource_type}. Must be one of {valid_resource_types}'
|
|
112
|
+
)
|
|
113
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
114
|
+
|
|
115
|
+
return MetricsGuidanceResponse(
|
|
116
|
+
isError=True,
|
|
117
|
+
content=[TextContent(type='text', text=error_message)],
|
|
118
|
+
resource_type=resource_type,
|
|
119
|
+
metrics=[],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
metrics = self.metrics_guidance.get(resource_type.lower(), {}).get('metrics', [])
|
|
123
|
+
resource_type_lower = resource_type.lower()
|
|
124
|
+
|
|
125
|
+
log_with_request_id(
|
|
126
|
+
ctx,
|
|
127
|
+
LogLevel.INFO,
|
|
128
|
+
f'Retrieved {len(metrics)} metrics for resource type {resource_type_lower}',
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return MetricsGuidanceResponse(
|
|
132
|
+
isError=False,
|
|
133
|
+
content=[
|
|
134
|
+
TextContent(
|
|
135
|
+
type='text',
|
|
136
|
+
text=f'Successfully retrieved {len(metrics)} metrics for resource type {resource_type_lower}',
|
|
137
|
+
)
|
|
138
|
+
],
|
|
139
|
+
resource_type=resource_type_lower,
|
|
140
|
+
metrics=metrics,
|
|
141
|
+
)
|
awslabs/eks_mcp_server/consts.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
11
14
|
|
|
12
15
|
"""Constants for the EKS MCP Server."""
|
|
13
16
|
|