awslabs.cloudwatch-mcp-server 0.0.10__py3-none-any.whl → 0.0.13__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/cloudwatch_mcp_server/__init__.py +1 -1
- awslabs/cloudwatch_mcp_server/cloudwatch_alarms/models.py +1 -1
- awslabs/cloudwatch_mcp_server/cloudwatch_alarms/tools.py +2 -2
- awslabs/cloudwatch_mcp_server/cloudwatch_logs/tools.py +59 -10
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/cloudformation_template_generator.py +162 -0
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/constants.py +30 -0
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/metric_analyzer.py +192 -0
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/metric_data_decomposer.py +218 -0
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/models.py +129 -3
- awslabs/cloudwatch_mcp_server/cloudwatch_metrics/tools.py +365 -31
- {awslabs_cloudwatch_mcp_server-0.0.10.dist-info → awslabs_cloudwatch_mcp_server-0.0.13.dist-info}/METADATA +6 -2
- awslabs_cloudwatch_mcp_server-0.0.13.dist-info/RECORD +21 -0
- awslabs_cloudwatch_mcp_server-0.0.10.dist-info/RECORD +0 -17
- {awslabs_cloudwatch_mcp_server-0.0.10.dist-info → awslabs_cloudwatch_mcp_server-0.0.13.dist-info}/WHEEL +0 -0
- {awslabs_cloudwatch_mcp_server-0.0.10.dist-info → awslabs_cloudwatch_mcp_server-0.0.13.dist-info}/entry_points.txt +0 -0
- {awslabs_cloudwatch_mcp_server-0.0.10.dist-info → awslabs_cloudwatch_mcp_server-0.0.13.dist-info}/licenses/LICENSE +0 -0
- {awslabs_cloudwatch_mcp_server-0.0.10.dist-info → awslabs_cloudwatch_mcp_server-0.0.13.dist-info}/licenses/NOTICE +0 -0
|
@@ -18,19 +18,32 @@ import boto3
|
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
20
|
from awslabs.cloudwatch_mcp_server import MCP_SERVER_VERSION
|
|
21
|
+
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.cloudformation_template_generator import (
|
|
22
|
+
CloudFormationTemplateGenerator,
|
|
23
|
+
)
|
|
24
|
+
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.constants import (
|
|
25
|
+
COMPARISON_OPERATOR_ANOMALY,
|
|
26
|
+
DEFAULT_ANALYSIS_PERIOD_MINUTES,
|
|
27
|
+
)
|
|
28
|
+
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.metric_analyzer import MetricAnalyzer
|
|
29
|
+
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.metric_data_decomposer import Seasonality
|
|
21
30
|
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.models import (
|
|
22
31
|
AlarmRecommendation,
|
|
23
32
|
AlarmRecommendationDimension,
|
|
33
|
+
AlarmRecommendationResult,
|
|
24
34
|
AlarmRecommendationThreshold,
|
|
35
|
+
AnomalyDetectionAlarmThreshold,
|
|
25
36
|
Dimension,
|
|
26
37
|
GetMetricDataResponse,
|
|
38
|
+
MetricData,
|
|
27
39
|
MetricDataPoint,
|
|
28
40
|
MetricDataResult,
|
|
29
41
|
MetricMetadata,
|
|
30
42
|
MetricMetadataIndexKey,
|
|
43
|
+
StaticAlarmThreshold,
|
|
31
44
|
)
|
|
32
45
|
from botocore.config import Config
|
|
33
|
-
from datetime import datetime
|
|
46
|
+
from datetime import datetime, timedelta, timezone
|
|
34
47
|
from loguru import logger
|
|
35
48
|
from mcp.server.fastmcp import Context
|
|
36
49
|
from pathlib import Path
|
|
@@ -48,6 +61,8 @@ class CloudWatchMetricsTools:
|
|
|
48
61
|
self._load_and_index_metadata()
|
|
49
62
|
)
|
|
50
63
|
logger.info(f'Loaded {len(self.metric_metadata_index)} metric metadata entries')
|
|
64
|
+
self.cloudformation_generator = CloudFormationTemplateGenerator()
|
|
65
|
+
self.metric_analyzer = MetricAnalyzer()
|
|
51
66
|
|
|
52
67
|
def _get_cloudwatch_client(self, region: str):
|
|
53
68
|
"""Create a CloudWatch client for the specified region."""
|
|
@@ -136,6 +151,9 @@ class CloudWatchMetricsTools:
|
|
|
136
151
|
# Register get_metric_metadata tool
|
|
137
152
|
mcp.tool(name='get_metric_metadata')(self.get_metric_metadata)
|
|
138
153
|
|
|
154
|
+
# Register analyze_metric tool
|
|
155
|
+
mcp.tool(name='analyze_metric')(self.analyze_metric)
|
|
156
|
+
|
|
139
157
|
# Register get_recommended_metric_alarms tool
|
|
140
158
|
mcp.tool(name='get_recommended_metric_alarms')(self.get_recommended_metric_alarms)
|
|
141
159
|
|
|
@@ -669,7 +687,22 @@ class CloudWatchMetricsTools:
|
|
|
669
687
|
description='AWS region for consistency. Note: This function uses local metadata and does not make AWS API calls. Defaults to us-east-1.'
|
|
670
688
|
),
|
|
671
689
|
] = 'us-east-1',
|
|
672
|
-
|
|
690
|
+
statistic: Annotated[
|
|
691
|
+
Literal[
|
|
692
|
+
'AVG',
|
|
693
|
+
'COUNT',
|
|
694
|
+
'MAX',
|
|
695
|
+
'MIN',
|
|
696
|
+
'SUM',
|
|
697
|
+
'Average',
|
|
698
|
+
'Sum',
|
|
699
|
+
'Maximum',
|
|
700
|
+
'Minimum',
|
|
701
|
+
'SampleCount',
|
|
702
|
+
],
|
|
703
|
+
Field(description='The statistic to use for alarm recommendations'),
|
|
704
|
+
] = 'AVG',
|
|
705
|
+
) -> AlarmRecommendationResult:
|
|
673
706
|
"""Gets recommended alarms for a CloudWatch metric.
|
|
674
707
|
|
|
675
708
|
This tool retrieves alarm recommendations for a specific CloudWatch metric
|
|
@@ -685,11 +718,19 @@ class CloudWatchMetricsTools:
|
|
|
685
718
|
metric_name: The name of the metric (e.g., "CPUUtilization", "Duration")
|
|
686
719
|
dimensions: List of dimensions with name and value pairs
|
|
687
720
|
region: AWS region to query. Defaults to 'us-east-1'.
|
|
721
|
+
statistic: The statistic to use for alarm recommendations. Must match the metric's data type:
|
|
722
|
+
- Aggregate count metrics (RequestCount, Errors, Faults, Throttles, CacheHits, Connections, EventsProcessed): Use 'Sum'
|
|
723
|
+
- Event occurrence metrics (Invocations, CacheMisses): Use 'SampleCount'
|
|
724
|
+
- Utilization metrics (CPUUtilization, MemoryUtilization, DiskUtilization, NetworkUtilization): Use 'Average'
|
|
725
|
+
- Latency/Time metrics (Duration, Latency, ResponseTime, ProcessingTime, Delay, ExecutionTime, WaitTime): Use 'Average'
|
|
726
|
+
- Size metrics (PayloadSize, MessageSize, RequestSize, BodySize): Use 'Average'
|
|
727
|
+
If uncertain about the correct statistic for a custom metric, ask the user
|
|
728
|
+
to confirm the metric type before generating recommendations. Using the wrong statistic
|
|
729
|
+
(e.g., 'Average' on Invocations) will produce ineffective alarm thresholds
|
|
688
730
|
|
|
689
731
|
Returns:
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
are found or available.
|
|
732
|
+
AlarmRecommendationResult: A result containing alarm recommendations and optional message.
|
|
733
|
+
Empty recommendations list if no recommendations are found.
|
|
693
734
|
|
|
694
735
|
Example:
|
|
695
736
|
recommendations = await get_recommended_metric_alarms(
|
|
@@ -714,12 +755,12 @@ class CloudWatchMetricsTools:
|
|
|
714
755
|
|
|
715
756
|
if not metadata or 'alarmRecommendations' not in metadata:
|
|
716
757
|
logger.info(f'No alarm recommendations found for {namespace}/{metric_name}')
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
758
|
+
alarm_recommendations = []
|
|
759
|
+
else:
|
|
760
|
+
alarm_recommendations = metadata['alarmRecommendations']
|
|
761
|
+
logger.info(
|
|
762
|
+
f'Found {len(alarm_recommendations)} alarm recommendations for {namespace}/{metric_name}'
|
|
763
|
+
)
|
|
723
764
|
|
|
724
765
|
# Filter recommendations based on provided dimensions
|
|
725
766
|
matching_recommendations = []
|
|
@@ -735,11 +776,58 @@ class CloudWatchMetricsTools:
|
|
|
735
776
|
logger.warning(f'Error parsing alarm recommendation: {e}')
|
|
736
777
|
continue
|
|
737
778
|
|
|
738
|
-
|
|
739
|
-
|
|
779
|
+
if len(matching_recommendations) > 0:
|
|
780
|
+
logger.info(
|
|
781
|
+
f'Found {len(matching_recommendations)} matching alarm recommendations'
|
|
782
|
+
)
|
|
783
|
+
return AlarmRecommendationResult(
|
|
784
|
+
recommendations=matching_recommendations,
|
|
785
|
+
message=f'Found {len(matching_recommendations)} matching alarm recommendations',
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# Generate additional recommendations based on metric analysis
|
|
789
|
+
additional_recommendations = []
|
|
790
|
+
logger.info('No predefined recommendations found - performing metric analysis')
|
|
791
|
+
analysis_result = await self.analyze_metric(
|
|
792
|
+
ctx,
|
|
793
|
+
namespace,
|
|
794
|
+
metric_name,
|
|
795
|
+
dimensions,
|
|
796
|
+
region,
|
|
797
|
+
statistic,
|
|
740
798
|
)
|
|
741
|
-
return matching_recommendations
|
|
742
799
|
|
|
800
|
+
# Generate additional recommendations based on seasonality
|
|
801
|
+
seasonality_value = analysis_result.get('seasonality_seconds', 0)
|
|
802
|
+
seasonality = Seasonality.from_seconds(seasonality_value)
|
|
803
|
+
|
|
804
|
+
if seasonality != Seasonality.NONE:
|
|
805
|
+
anomaly_detector_data = self._create_anomaly_detector_data(
|
|
806
|
+
metric_name=metric_name,
|
|
807
|
+
namespace=namespace,
|
|
808
|
+
dimensions=dimensions,
|
|
809
|
+
seasonality=seasonality,
|
|
810
|
+
)
|
|
811
|
+
alarm_rec = self._parse_alarm_recommendation(anomaly_detector_data)
|
|
812
|
+
additional_recommendations.append(alarm_rec)
|
|
813
|
+
logger.info(
|
|
814
|
+
f'Recommended anomaly detection alarm due to seasonality: {seasonality.name}'
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
if len(additional_recommendations) > 0:
|
|
818
|
+
message = f'Generated {len(additional_recommendations)} alarm recommendation(s) for {namespace}/{metric_name} based on metric analysis'
|
|
819
|
+
logger.info(message)
|
|
820
|
+
return AlarmRecommendationResult(
|
|
821
|
+
recommendations=additional_recommendations,
|
|
822
|
+
message=message,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
message = f'No alarm recommendations available for {namespace}/{metric_name} with the provided dimensions'
|
|
826
|
+
logger.info(message)
|
|
827
|
+
return AlarmRecommendationResult(
|
|
828
|
+
recommendations=[],
|
|
829
|
+
message=message,
|
|
830
|
+
)
|
|
743
831
|
except Exception as e:
|
|
744
832
|
logger.error(f'Error in get_recommended_metric_alarms: {str(e)}')
|
|
745
833
|
await ctx.error(f'Error getting alarm recommendations: {str(e)}')
|
|
@@ -781,6 +869,30 @@ class CloudWatchMetricsTools:
|
|
|
781
869
|
|
|
782
870
|
return True
|
|
783
871
|
|
|
872
|
+
def _create_alarm_threshold(
|
|
873
|
+
self, threshold_data: Dict[str, Any]
|
|
874
|
+
) -> AlarmRecommendationThreshold:
|
|
875
|
+
"""Create threshold object from threshold data.
|
|
876
|
+
|
|
877
|
+
Args:
|
|
878
|
+
threshold_data: Raw alarm threshold data
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
AlarmRecommendationThreshold: Appropriate threshold object based on threshold type.
|
|
882
|
+
"""
|
|
883
|
+
if 'sensitivity' in threshold_data:
|
|
884
|
+
return AnomalyDetectionAlarmThreshold(
|
|
885
|
+
sensitivity=threshold_data.get(
|
|
886
|
+
'sensitivity', AnomalyDetectionAlarmThreshold.DEFAULT_SENSITIVITY
|
|
887
|
+
),
|
|
888
|
+
justification=threshold_data.get('justification', ''),
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
return StaticAlarmThreshold(
|
|
892
|
+
staticValue=threshold_data.get('staticValue', 0.0),
|
|
893
|
+
justification=threshold_data.get('justification', ''),
|
|
894
|
+
)
|
|
895
|
+
|
|
784
896
|
def _parse_alarm_recommendation(self, alarm_data: Dict[str, Any]) -> AlarmRecommendation:
|
|
785
897
|
"""Parse alarm recommendation data into AlarmRecommendation object.
|
|
786
898
|
|
|
@@ -792,12 +904,105 @@ class CloudWatchMetricsTools:
|
|
|
792
904
|
"""
|
|
793
905
|
# Parse threshold
|
|
794
906
|
threshold_data = alarm_data.get('threshold', {})
|
|
795
|
-
threshold =
|
|
796
|
-
|
|
797
|
-
|
|
907
|
+
threshold = self._create_alarm_threshold(threshold_data)
|
|
908
|
+
|
|
909
|
+
# Generate CloudFormation template only for anomaly detection alarms
|
|
910
|
+
cfn_template = self.cloudformation_generator.generate_metric_alarm_template(alarm_data)
|
|
911
|
+
|
|
912
|
+
# Build alarm recommendation kwargs
|
|
913
|
+
alarm_kwargs = {
|
|
914
|
+
'alarmDescription': alarm_data.get('alarmDescription', ''),
|
|
915
|
+
'metricName': alarm_data.get('metricName', ''),
|
|
916
|
+
'namespace': alarm_data.get('namespace', ''),
|
|
917
|
+
'threshold': threshold,
|
|
918
|
+
'period': alarm_data.get('period', 300),
|
|
919
|
+
'comparisonOperator': alarm_data.get('comparisonOperator', ''),
|
|
920
|
+
'statistic': alarm_data.get('statistic', ''),
|
|
921
|
+
'evaluationPeriods': alarm_data.get('evaluationPeriods', 1),
|
|
922
|
+
'datapointsToAlarm': alarm_data.get('datapointsToAlarm', 1),
|
|
923
|
+
'treatMissingData': alarm_data.get('treatMissingData', 'missing'),
|
|
924
|
+
'dimensions': self._parse_metric_dimensions(alarm_data),
|
|
925
|
+
'intent': alarm_data.get('intent', ''),
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
# Only include cloudformation_template if it was successfully generated
|
|
929
|
+
if cfn_template:
|
|
930
|
+
alarm_kwargs['cloudformation_template'] = cfn_template
|
|
931
|
+
|
|
932
|
+
return AlarmRecommendation(**alarm_kwargs)
|
|
933
|
+
|
|
934
|
+
def _create_anomaly_detector_data(
|
|
935
|
+
self,
|
|
936
|
+
metric_name: str,
|
|
937
|
+
namespace: str,
|
|
938
|
+
dimensions: List[Dimension],
|
|
939
|
+
seasonality: Seasonality,
|
|
940
|
+
) -> Dict[str, Any]:
|
|
941
|
+
"""Format Anomaly Detector data for use in alarm creation.
|
|
942
|
+
|
|
943
|
+
Args:
|
|
944
|
+
metric_name: The metric name
|
|
945
|
+
namespace: The metric namespace
|
|
946
|
+
dimensions: List of metric dimensions
|
|
947
|
+
seasonality: Detected seasonality
|
|
948
|
+
|
|
949
|
+
Returns:
|
|
950
|
+
Dict[str, Any]: Anomaly detector formatted data
|
|
951
|
+
"""
|
|
952
|
+
# Create alarm data structure for _parse_alarm_recommendation
|
|
953
|
+
return {
|
|
954
|
+
'alarmDescription': f'Anomaly detection alarm for {namespace}/{metric_name} (seasonality {seasonality.name})',
|
|
955
|
+
'statistic': 'Average',
|
|
956
|
+
'dimensions': [{'Name': dim.name, 'Value': dim.value} for dim in dimensions],
|
|
957
|
+
'threshold': {
|
|
958
|
+
'sensitivity': AnomalyDetectionAlarmThreshold.DEFAULT_SENSITIVITY,
|
|
959
|
+
'justification': f'Metric has a seasonality of {seasonality.name} making it suitable for Anomaly Detection.',
|
|
960
|
+
},
|
|
961
|
+
'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
|
|
962
|
+
'evaluationPeriods': 2,
|
|
963
|
+
'datapointsToAlarm': 2,
|
|
964
|
+
'period': 300,
|
|
965
|
+
'treatMissingData': 'missing',
|
|
966
|
+
'intent': f'Detect anomalies in {metric_name} based on {seasonality.name} seasonal length',
|
|
967
|
+
'metricName': metric_name,
|
|
968
|
+
'namespace': namespace,
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
def _create_anomaly_detector_recommendation(
|
|
972
|
+
self,
|
|
973
|
+
metric_name: str,
|
|
974
|
+
namespace: str,
|
|
975
|
+
dimensions: List[Dimension],
|
|
976
|
+
seasonality: Seasonality,
|
|
977
|
+
) -> AlarmRecommendation:
|
|
978
|
+
"""Create an anomaly detector recommendation.
|
|
979
|
+
|
|
980
|
+
Args:
|
|
981
|
+
metric_name: The metric name
|
|
982
|
+
namespace: The metric namespace
|
|
983
|
+
dimensions: List of metric dimensions
|
|
984
|
+
seasonality: Detected seasonality
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
AlarmRecommendation: Anomaly detector alarm recommendation
|
|
988
|
+
"""
|
|
989
|
+
alarm_data = self._create_anomaly_detector_data(
|
|
990
|
+
metric_name=metric_name,
|
|
991
|
+
namespace=namespace,
|
|
992
|
+
dimensions=dimensions,
|
|
993
|
+
seasonality=seasonality,
|
|
798
994
|
)
|
|
995
|
+
return self._parse_alarm_recommendation(alarm_data)
|
|
996
|
+
|
|
997
|
+
def _parse_metric_dimensions(self, alarm_data: Dict[str, Any]) -> List[str]:
|
|
998
|
+
"""Parse metric dimensions from the alarm data.
|
|
799
999
|
|
|
800
|
-
|
|
1000
|
+
Args:
|
|
1001
|
+
alarm_data: Raw alarm recommendation data
|
|
1002
|
+
|
|
1003
|
+
Returns:
|
|
1004
|
+
AlarmRecommendation: Parsed alarm recommendation object
|
|
1005
|
+
"""
|
|
801
1006
|
dimensions = []
|
|
802
1007
|
for dim_data in alarm_data.get('dimensions', []):
|
|
803
1008
|
alarm_dim = AlarmRecommendationDimension(
|
|
@@ -806,16 +1011,145 @@ class CloudWatchMetricsTools:
|
|
|
806
1011
|
)
|
|
807
1012
|
dimensions.append(alarm_dim)
|
|
808
1013
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1014
|
+
return dimensions
|
|
1015
|
+
|
|
1016
|
+
def _parse_metric_data_response(
|
|
1017
|
+
self, response: GetMetricDataResponse, period_seconds: int
|
|
1018
|
+
) -> MetricData:
|
|
1019
|
+
"""Parse CloudWatch GetMetricData response into MetricData."""
|
|
1020
|
+
timestamps = []
|
|
1021
|
+
values = []
|
|
1022
|
+
|
|
1023
|
+
if response.metricDataResults and response.metricDataResults[0].datapoints:
|
|
1024
|
+
datapoints = response.metricDataResults[0].datapoints
|
|
1025
|
+
timestamps_ms = [int(dp.timestamp.timestamp() * 1000) for dp in datapoints]
|
|
1026
|
+
raw_values = [dp.value for dp in datapoints]
|
|
1027
|
+
|
|
1028
|
+
sorted_data = sorted(zip(timestamps_ms, raw_values))
|
|
1029
|
+
if sorted_data:
|
|
1030
|
+
timestamps, values = zip(*sorted_data)
|
|
1031
|
+
timestamps = list(timestamps)
|
|
1032
|
+
values = list(values)
|
|
1033
|
+
|
|
1034
|
+
return MetricData(period_seconds=period_seconds, timestamps=timestamps, values=values)
|
|
1035
|
+
|
|
1036
|
+
async def analyze_metric(
|
|
1037
|
+
self,
|
|
1038
|
+
ctx: Context,
|
|
1039
|
+
namespace: str = Field(
|
|
1040
|
+
..., description="The namespace of the metric (e.g., 'AWS/EC2', 'AWS/Lambda')"
|
|
1041
|
+
),
|
|
1042
|
+
metric_name: str = Field(
|
|
1043
|
+
..., description="The name of the metric (e.g., 'CPUUtilization', 'Duration')"
|
|
1044
|
+
),
|
|
1045
|
+
dimensions: List[Dimension] = Field(
|
|
1046
|
+
default_factory=list,
|
|
1047
|
+
description='List of dimensions that identify the metric, each with name and value',
|
|
1048
|
+
),
|
|
1049
|
+
region: Annotated[
|
|
1050
|
+
str,
|
|
1051
|
+
Field(description='AWS region to query. Defaults to us-east-1.'),
|
|
1052
|
+
] = 'us-east-1',
|
|
1053
|
+
statistic: Annotated[
|
|
1054
|
+
Literal[
|
|
1055
|
+
'AVG',
|
|
1056
|
+
'COUNT',
|
|
1057
|
+
'MAX',
|
|
1058
|
+
'MIN',
|
|
1059
|
+
'SUM',
|
|
1060
|
+
'Average',
|
|
1061
|
+
'Sum',
|
|
1062
|
+
'Maximum',
|
|
1063
|
+
'Minimum',
|
|
1064
|
+
'SampleCount',
|
|
1065
|
+
],
|
|
1066
|
+
Field(description='The statistic to use for the metric analysis'),
|
|
1067
|
+
] = 'AVG',
|
|
1068
|
+
) -> Dict[str, Any]:
|
|
1069
|
+
"""Analyzes CloudWatch metric data to determine seasonality, trend, data density and statistical properties.
|
|
1070
|
+
|
|
1071
|
+
This tool provides RAW DATA ONLY about historical metric data and performs analysis including:
|
|
1072
|
+
- Seasonality detection
|
|
1073
|
+
- Trend analysis
|
|
1074
|
+
- Data density and publishing period
|
|
1075
|
+
- Advanced statistical measures (min/max/median, std dev, noise)
|
|
1076
|
+
|
|
1077
|
+
Usage: Use this tool to get objective metric analysis data.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
ctx: The MCP context object for error handling and logging.
|
|
1081
|
+
namespace: The metric namespace (e.g., "AWS/EC2", "AWS/Lambda")
|
|
1082
|
+
metric_name: The name of the metric (e.g., "CPUUtilization", "Duration")
|
|
1083
|
+
dimensions: List of dimensions with name and value pairs
|
|
1084
|
+
statistic: The statistic to use for metric analysis. For guidance on choosing the correct statistic, refer to the get_recommended_metric_alarms tool.
|
|
1085
|
+
region: AWS region to query. Defaults to 'us-east-1'.
|
|
1086
|
+
|
|
1087
|
+
Returns:
|
|
1088
|
+
Dict[str, Any]: Analysis results including:
|
|
1089
|
+
- message: Status message indicating success or reason for empty result
|
|
1090
|
+
- seasonality_seconds: Detected seasonality period in seconds
|
|
1091
|
+
- trend: Trend direction (INCREASING, DECREASING, or NONE)
|
|
1092
|
+
- statistics: Statistical measures (std_deviation, variance, etc.)
|
|
1093
|
+
- data_quality: Data density and publishing period information
|
|
1094
|
+
|
|
1095
|
+
Example:
|
|
1096
|
+
analysis = await analyze_metric(
|
|
1097
|
+
ctx,
|
|
1098
|
+
namespace="AWS/EC2",
|
|
1099
|
+
metric_name="CPUUtilization",
|
|
1100
|
+
dimensions=[
|
|
1101
|
+
Dimension(name="InstanceId", value="i-1234567890abcdef0")
|
|
1102
|
+
]
|
|
1103
|
+
)
|
|
1104
|
+
print(f"Status: {analysis['message']}")
|
|
1105
|
+
print(f"Seasonality: {analysis['seasonality_seconds']} seconds")
|
|
1106
|
+
print(f"Trend: {analysis['trend']}")
|
|
1107
|
+
"""
|
|
1108
|
+
try:
|
|
1109
|
+
analysis_period_minutes = DEFAULT_ANALYSIS_PERIOD_MINUTES
|
|
1110
|
+
|
|
1111
|
+
logger.info(f'Analyzing metric: {namespace}/{metric_name} in region {region}')
|
|
1112
|
+
|
|
1113
|
+
end_time = datetime.now(timezone.utc)
|
|
1114
|
+
start_time = end_time - timedelta(minutes=analysis_period_minutes)
|
|
1115
|
+
|
|
1116
|
+
metric_data_response = await self.get_metric_data(
|
|
1117
|
+
ctx=ctx,
|
|
1118
|
+
namespace=namespace,
|
|
1119
|
+
metric_name=metric_name,
|
|
1120
|
+
dimensions=dimensions,
|
|
1121
|
+
start_time=start_time.isoformat(),
|
|
1122
|
+
end_time=end_time.isoformat(),
|
|
1123
|
+
statistic=statistic,
|
|
1124
|
+
region=region,
|
|
1125
|
+
target_datapoints=analysis_period_minutes,
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
# Parse response into structured data
|
|
1129
|
+
_, _, period_seconds = self._prepare_time_parameters(
|
|
1130
|
+
start_time, end_time, analysis_period_minutes
|
|
1131
|
+
)
|
|
1132
|
+
metric_data = self._parse_metric_data_response(metric_data_response, period_seconds)
|
|
1133
|
+
analysis_result = self.metric_analyzer.analyze_metric_data(metric_data)
|
|
1134
|
+
|
|
1135
|
+
analysis_result.update(
|
|
1136
|
+
{
|
|
1137
|
+
'metric_info': {
|
|
1138
|
+
'namespace': namespace,
|
|
1139
|
+
'metric_name': metric_name,
|
|
1140
|
+
'statistic': statistic,
|
|
1141
|
+
'dimensions': [{'name': d.name, 'value': d.value} for d in dimensions],
|
|
1142
|
+
'analysis_period_minutes': analysis_period_minutes,
|
|
1143
|
+
'time_range': {
|
|
1144
|
+
'start': start_time.isoformat(),
|
|
1145
|
+
'end': end_time.isoformat(),
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
}
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
return analysis_result
|
|
1152
|
+
except Exception as e:
|
|
1153
|
+
logger.error(f'Error in analyze_metric: {str(e)}')
|
|
1154
|
+
await ctx.error(f'Error encountered when analyzing metric: {str(e)}')
|
|
1155
|
+
raise
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awslabs.cloudwatch-mcp-server
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.13
|
|
4
4
|
Summary: An AWS Labs Model Context Protocol (MCP) server for cloudwatch
|
|
5
5
|
Project-URL: homepage, https://awslabs.github.io/mcp/
|
|
6
6
|
Project-URL: docs, https://awslabs.github.io/mcp/servers/cloudwatch-mcp-server/
|
|
@@ -24,7 +24,10 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Requires-Dist: boto3>=1.38.22
|
|
25
25
|
Requires-Dist: loguru>=0.7.0
|
|
26
26
|
Requires-Dist: mcp[cli]>=1.11.0
|
|
27
|
+
Requires-Dist: numpy>=2.0.0
|
|
28
|
+
Requires-Dist: pandas>=2.2.3
|
|
27
29
|
Requires-Dist: pydantic>=2.10.6
|
|
30
|
+
Requires-Dist: statsmodels>=0.14.0
|
|
28
31
|
Description-Content-Type: text/markdown
|
|
29
32
|
|
|
30
33
|
# AWS Labs cloudwatch MCP Server
|
|
@@ -57,7 +60,8 @@ Alarm Recommendations - Suggests recommended alarm configurations for CloudWatch
|
|
|
57
60
|
### Tools for CloudWatch Metrics
|
|
58
61
|
* `get_metric_data` - Retrieves detailed CloudWatch metric data for any CloudWatch metric. Use this for general CloudWatch metrics that aren't specific to Application Signals. Provides ability to query any metric namespace, dimension, and statistic
|
|
59
62
|
* `get_metric_metadata` - Retrieves comprehensive metadata about a specific CloudWatch metric
|
|
60
|
-
* `get_recommended_metric_alarms` - Gets recommended alarms for a CloudWatch metric
|
|
63
|
+
* `get_recommended_metric_alarms` - Gets recommended alarms for a CloudWatch metric based on best practice, and trend, seasonality and statistical analysis.
|
|
64
|
+
* `analyze_metric` - Analyzes CloudWatch metric data to determine trend, seasonality, and statistical properties
|
|
61
65
|
|
|
62
66
|
### Tools for CloudWatch Alarms
|
|
63
67
|
* `get_active_alarms` - Identifies currently active CloudWatch alarms across the account
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
awslabs/__init__.py,sha256=WuqxdDgUZylWNmVoPKiK7qGsTB_G4UmuXIrJ-VBwDew,731
|
|
2
|
+
awslabs/cloudwatch_mcp_server/__init__.py,sha256=vqzwA3x7riO_70o_CNV6Xmbi_D1krbM0JVMe4v59lI0,683
|
|
3
|
+
awslabs/cloudwatch_mcp_server/common.py,sha256=dbnaQlIOh4VmJvflzoJXv4JkrK7brPa1hgnrfLapJDs,2190
|
|
4
|
+
awslabs/cloudwatch_mcp_server/server.py,sha256=raMeiC0F8M7cIH6-5rZnU2_DZZkPEtpvRm-5_hd9tbs,2505
|
|
5
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_alarms/models.py,sha256=wgwSE4JqLa0K7-3xhxGjDvGetXi-TZkmjbWtXRvx7CA,7202
|
|
6
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_alarms/tools.py,sha256=ftllhM1ORWpA65FGG_oPH7h9UiGwakFCJ03lqUzrVSg,30865
|
|
7
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_logs/models.py,sha256=wyfxHNN5E2K49LreqKBMAZ4Vyvc2MMkxMCfyGHsK-pQ,7982
|
|
8
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_logs/tools.py,sha256=b0tobrfTkyEzKkqof0wNR649PWfOIA0euGSwT4qvevM,30018
|
|
9
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/cloudformation_template_generator.py,sha256=eHnIpd5DBrVjXpmjy1n7ZxfKZA-9JkwuxogcBUg4E4I,6944
|
|
10
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/constants.py,sha256=v1IETRSHtckgVqjm6WgzWEi0fqYqwMrIL3M3V9k1K5A,981
|
|
11
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/metric_analyzer.py,sha256=t0cmrxbIPbw7gjWQXydYWxuhKnRbH_bbyzqkm7itFag,7601
|
|
12
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/metric_data_decomposer.py,sha256=SzB7CyyXvSIk7Qfd-5OyHAThFPjk5N3ZDbx37kjVsSs,8361
|
|
13
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/models.py,sha256=38ppi8euC-PurLypCAbBxyrIgeqja-5pOImQLPukDDo,10175
|
|
14
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/tools.py,sha256=v5orz1PICegO6A059CMyQd0Q3nwYNRtKcJHmD51Hh08,47807
|
|
15
|
+
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/data/metric_metadata.json,sha256=FlSUlfYVo1VdANrsBg50atggMNGSqZvhhZU7ca-v48c,745055
|
|
16
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/METADATA,sha256=Z2TXYNOvczxpmqNSg09jwSsFIjTTCQCkI2nJp8rNRJM,10393
|
|
17
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/entry_points.txt,sha256=u8YZn3_qq9Ai7nRux17K6K7RDssOBhTSyUNyZKrQUIk,92
|
|
19
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
|
20
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/licenses/NOTICE,sha256=xpvRUSHqG7LIpUZ1W_0D4sfPJG8lj51X6Mxjq1KJ6eQ,97
|
|
21
|
+
awslabs_cloudwatch_mcp_server-0.0.13.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
awslabs/__init__.py,sha256=WuqxdDgUZylWNmVoPKiK7qGsTB_G4UmuXIrJ-VBwDew,731
|
|
2
|
-
awslabs/cloudwatch_mcp_server/__init__.py,sha256=T6q1y13qcCJuD_fMN_CVKPf--GyUP6RcKNdS68ZYLpY,682
|
|
3
|
-
awslabs/cloudwatch_mcp_server/common.py,sha256=dbnaQlIOh4VmJvflzoJXv4JkrK7brPa1hgnrfLapJDs,2190
|
|
4
|
-
awslabs/cloudwatch_mcp_server/server.py,sha256=raMeiC0F8M7cIH6-5rZnU2_DZZkPEtpvRm-5_hd9tbs,2505
|
|
5
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_alarms/models.py,sha256=vqctogiT-EjzA_VczWUYeskaF_aAOBQJR_s5KI7NkuI,7202
|
|
6
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_alarms/tools.py,sha256=PGEm8alzSAbrz-X-JaWARGPHKU1IBXDTmNP8R7PZPfc,30865
|
|
7
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_logs/models.py,sha256=wyfxHNN5E2K49LreqKBMAZ4Vyvc2MMkxMCfyGHsK-pQ,7982
|
|
8
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_logs/tools.py,sha256=ohaVlRsmXlg8yJOL1D-KEXPPuXfJ9mVSdj8YKc-xeBw,28163
|
|
9
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/models.py,sha256=ov6aJUUrN2fz9Gom-tE4DT5h6M0PCkKwA9pEBnVv6SE,5756
|
|
10
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/tools.py,sha256=GoW0gLy35gW7MspbxtfNBghUgR8xWIa4tgcohqYE1h8,33410
|
|
11
|
-
awslabs/cloudwatch_mcp_server/cloudwatch_metrics/data/metric_metadata.json,sha256=FlSUlfYVo1VdANrsBg50atggMNGSqZvhhZU7ca-v48c,745055
|
|
12
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/METADATA,sha256=kXSMICub-jPW9BkggBDo26IfkThHdvVK3thonk5eaSM,10115
|
|
13
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/entry_points.txt,sha256=u8YZn3_qq9Ai7nRux17K6K7RDssOBhTSyUNyZKrQUIk,92
|
|
15
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
|
16
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/licenses/NOTICE,sha256=xpvRUSHqG7LIpUZ1W_0D4sfPJG8lj51X6Mxjq1KJ6eQ,97
|
|
17
|
-
awslabs_cloudwatch_mcp_server-0.0.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|