awslabs.cloudwatch-applicationsignals-mcp-server 0.1.21__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 +17 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/__init__.py +17 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/audit_presentation_utils.py +288 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/audit_utils.py +912 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/aws_clients.py +120 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/canary_utils.py +910 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ec2/ec2-dotnet-enablement.md +435 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ec2/ec2-java-enablement.md +321 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ec2/ec2-nodejs-enablement.md +420 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ec2/ec2-python-enablement.md +598 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ecs/ecs-dotnet-enablement.md +264 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ecs/ecs-java-enablement.md +193 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ecs/ecs-nodejs-enablement.md +198 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/ecs/ecs-python-enablement.md +236 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/eks/eks-dotnet-enablement.md +166 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/eks/eks-java-enablement.md +166 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/eks/eks-nodejs-enablement.md +166 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/eks/eks-python-enablement.md +169 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/lambda/lambda-dotnet-enablement.md +336 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/lambda/lambda-java-enablement.md +336 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/lambda/lambda-nodejs-enablement.md +336 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_guides/templates/lambda/lambda-python-enablement.md +336 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/enablement_tools.py +147 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/server.py +1505 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/service_audit_utils.py +231 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/service_tools.py +659 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/sli_report_client.py +333 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/slo_tools.py +386 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/trace_tools.py +784 -0
- awslabs/cloudwatch_applicationsignals_mcp_server/utils.py +172 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/METADATA +808 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/RECORD +36 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/WHEEL +4 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/entry_points.txt +2 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/licenses/LICENSE +174 -0
- awslabs_cloudwatch_applicationsignals_mcp_server-0.1.21.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,172 @@
|
|
|
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 Application Signals MCP Server - Utility functions."""
|
|
16
|
+
|
|
17
|
+
from datetime import datetime, timedelta, timezone
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def remove_null_values(data: dict) -> dict:
|
|
21
|
+
"""Remove keys with None values from a dictionary.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
data: Dictionary to clean
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dictionary with None values removed
|
|
28
|
+
"""
|
|
29
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_timestamp(timestamp_str: str, default_hours: int = 24) -> datetime:
|
|
33
|
+
"""Parse timestamp string into datetime object.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
timestamp_str: Timestamp in unix seconds or 'YYYY-MM-DD HH:MM:SS' format
|
|
37
|
+
default_hours: Default hours to subtract from now if parsing fails
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
datetime object in UTC timezone
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# Ensure we have a string
|
|
44
|
+
if not isinstance(timestamp_str, str):
|
|
45
|
+
timestamp_str = str(timestamp_str)
|
|
46
|
+
|
|
47
|
+
# Try parsing as unix timestamp first
|
|
48
|
+
if timestamp_str.isdigit():
|
|
49
|
+
return datetime.fromtimestamp(int(timestamp_str), tz=timezone.utc)
|
|
50
|
+
|
|
51
|
+
# Try parsing as ISO format
|
|
52
|
+
if 'T' in timestamp_str:
|
|
53
|
+
return datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
|
|
54
|
+
|
|
55
|
+
# Try parsing as 'YYYY-MM-DD HH:MM:SS' format
|
|
56
|
+
return datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc)
|
|
57
|
+
except (ValueError, TypeError):
|
|
58
|
+
# Fallback to default
|
|
59
|
+
return datetime.now(timezone.utc) - timedelta(hours=default_hours)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def calculate_name_similarity(
|
|
63
|
+
target_name: str, candidate_name: str, name_type: str = 'service'
|
|
64
|
+
) -> int:
|
|
65
|
+
"""Calculate similarity score between target name and candidate name.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
target_name: The name the user is looking for
|
|
69
|
+
candidate_name: A candidate name from the API
|
|
70
|
+
name_type: Type of name being matched ("service" or "slo")
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Similarity score (0-100, higher is better match)
|
|
74
|
+
"""
|
|
75
|
+
target_lower = target_name.lower().strip()
|
|
76
|
+
candidate_lower = candidate_name.lower().strip()
|
|
77
|
+
|
|
78
|
+
# Handle empty strings
|
|
79
|
+
if not target_lower or not candidate_lower:
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
# Exact match (case insensitive)
|
|
83
|
+
if target_lower == candidate_lower:
|
|
84
|
+
return 100
|
|
85
|
+
|
|
86
|
+
# Normalize for special characters (treat -, _, . as equivalent)
|
|
87
|
+
target_normalized = target_lower.replace('_', '-').replace('.', '-')
|
|
88
|
+
candidate_normalized = candidate_lower.replace('_', '-').replace('.', '-')
|
|
89
|
+
|
|
90
|
+
if target_normalized == candidate_normalized:
|
|
91
|
+
return 95
|
|
92
|
+
|
|
93
|
+
score = 0
|
|
94
|
+
|
|
95
|
+
# Word-based matching (most important for fuzzy matching)
|
|
96
|
+
target_words = set(target_normalized.split())
|
|
97
|
+
candidate_words = set(candidate_normalized.split())
|
|
98
|
+
|
|
99
|
+
if target_words and candidate_words:
|
|
100
|
+
common_words = target_words.intersection(candidate_words)
|
|
101
|
+
if common_words:
|
|
102
|
+
# Calculate word match ratio
|
|
103
|
+
word_match_ratio = len(common_words) / len(target_words.union(candidate_words))
|
|
104
|
+
score += int(word_match_ratio * 60) # Up to 60 points for word matches
|
|
105
|
+
|
|
106
|
+
# Bonus for high word overlap
|
|
107
|
+
target_coverage = len(common_words) / len(target_words)
|
|
108
|
+
|
|
109
|
+
if target_coverage >= 0.8: # 80% of target words found
|
|
110
|
+
score += 20
|
|
111
|
+
elif target_coverage >= 0.6: # 60% of target words found
|
|
112
|
+
score += 10
|
|
113
|
+
|
|
114
|
+
# Substring matching (secondary)
|
|
115
|
+
if target_normalized in candidate_normalized:
|
|
116
|
+
# Target is contained in candidate
|
|
117
|
+
containment_ratio = len(target_normalized) / len(candidate_normalized)
|
|
118
|
+
score += int(containment_ratio * 30) # Up to 30 points
|
|
119
|
+
elif candidate_normalized in target_normalized:
|
|
120
|
+
# Candidate is contained in target
|
|
121
|
+
containment_ratio = len(candidate_normalized) / len(target_normalized)
|
|
122
|
+
score += int(containment_ratio * 25) # Up to 25 points
|
|
123
|
+
|
|
124
|
+
# Check for key domain terms that should boost relevance
|
|
125
|
+
if name_type == 'slo':
|
|
126
|
+
key_terms = [
|
|
127
|
+
'availability',
|
|
128
|
+
'latency',
|
|
129
|
+
'error',
|
|
130
|
+
'fault',
|
|
131
|
+
'search',
|
|
132
|
+
'owner',
|
|
133
|
+
'response',
|
|
134
|
+
'time',
|
|
135
|
+
'success',
|
|
136
|
+
'failure',
|
|
137
|
+
'request',
|
|
138
|
+
'operation',
|
|
139
|
+
]
|
|
140
|
+
else: # service
|
|
141
|
+
key_terms = [
|
|
142
|
+
'service',
|
|
143
|
+
'api',
|
|
144
|
+
'web',
|
|
145
|
+
'app',
|
|
146
|
+
'backend',
|
|
147
|
+
'frontend',
|
|
148
|
+
'database',
|
|
149
|
+
'cache',
|
|
150
|
+
'queue',
|
|
151
|
+
'worker',
|
|
152
|
+
'lambda',
|
|
153
|
+
'function',
|
|
154
|
+
'microservice',
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
common_key_terms = 0
|
|
158
|
+
for term in key_terms:
|
|
159
|
+
if term in target_normalized and term in candidate_normalized:
|
|
160
|
+
common_key_terms += 1
|
|
161
|
+
|
|
162
|
+
if common_key_terms > 0:
|
|
163
|
+
score += common_key_terms * 8 # Up to 8 points per key term
|
|
164
|
+
|
|
165
|
+
# Penalize very different lengths (likely different concepts)
|
|
166
|
+
length_diff = abs(len(target_normalized) - len(candidate_normalized))
|
|
167
|
+
if length_diff > 20:
|
|
168
|
+
score = max(0, score - 15)
|
|
169
|
+
elif length_diff > 10:
|
|
170
|
+
score = max(0, score - 5)
|
|
171
|
+
|
|
172
|
+
return min(100, score)
|