awslabs.well-architected-security-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,173 @@
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
+ """Utilities for working with prompt templates."""
16
+
17
+ import os
18
+ import re
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ from loguru import logger
22
+
23
+ # Cache for prompt templates
24
+ _prompt_templates = {}
25
+ _is_initialized = False
26
+
27
+
28
+ def load_prompt_templates(file_path: str = "PROMPT_TEMPLATE.md") -> Dict[str, Dict[str, Any]]:
29
+ """Load prompt templates from a markdown file.
30
+
31
+ Args:
32
+ file_path: Path to the markdown file containing prompt templates
33
+
34
+ Returns:
35
+ Dictionary mapping template names to template content and metadata
36
+ """
37
+ global _prompt_templates, _is_initialized
38
+
39
+ if _is_initialized:
40
+ return _prompt_templates
41
+
42
+ try:
43
+ # Check if file exists
44
+ if not os.path.exists(file_path):
45
+ logger.error(f"Prompt template file not found: {file_path}")
46
+ return {}
47
+
48
+ # Read the file content
49
+ with open(file_path, "r") as f:
50
+ content = f.read()
51
+
52
+ # Parse the markdown content to extract templates
53
+ # First, split by level 2 headers (## )
54
+ sections = re.split(r"(?m)^## ", content)
55
+
56
+ # The first section is the introduction, skip it
57
+ if sections and not sections[0].startswith("## "):
58
+ sections = sections[1:]
59
+
60
+ # Process each section
61
+ for section in sections:
62
+ if not section.strip():
63
+ continue
64
+
65
+ # Extract the section title (template name)
66
+ lines = section.split("\n")
67
+ title = lines[0].strip()
68
+
69
+ # Convert title to a template name (lowercase, underscores)
70
+ template_name = title.lower().replace(" ", "_")
71
+
72
+ # Extract the template content (between triple backticks)
73
+ template_content = ""
74
+ description = ""
75
+ in_code_block = False
76
+ desc_lines = []
77
+
78
+ for line in lines[1:]:
79
+ if line.strip() == "```":
80
+ in_code_block = not in_code_block
81
+ continue
82
+
83
+ if in_code_block:
84
+ template_content += line + "\n"
85
+ elif line.strip() and not in_code_block:
86
+ desc_lines.append(line.strip())
87
+
88
+ # Join description lines
89
+ if desc_lines:
90
+ description = " ".join(desc_lines)
91
+
92
+ # Store the template
93
+ _prompt_templates[template_name] = {
94
+ "name": template_name,
95
+ "title": title,
96
+ "content": template_content.strip(),
97
+ "description": description,
98
+ }
99
+
100
+ # Special handling for the workflow example section
101
+ workflow_section = None
102
+ for section in content.split("## "):
103
+ if section.startswith("Example Workflow"):
104
+ workflow_section = section
105
+ break
106
+
107
+ if workflow_section:
108
+ _prompt_templates["workflow_example"] = {
109
+ "name": "workflow_example",
110
+ "title": "Example Workflow",
111
+ "content": workflow_section.split("Example Workflow")[1].strip(),
112
+ "description": "Recommended workflow for a comprehensive security assessment",
113
+ }
114
+
115
+ _is_initialized = True
116
+ logger.info(f"Loaded {len(_prompt_templates)} prompt templates from {file_path}")
117
+ return _prompt_templates
118
+
119
+ except Exception as e:
120
+ logger.error(f"Error loading prompt templates: {e}")
121
+ return {}
122
+
123
+
124
+ def get_prompt_template(template_name: str) -> Optional[Dict[str, Any]]:
125
+ """Get a specific prompt template by name.
126
+
127
+ Args:
128
+ template_name: Name of the template to retrieve
129
+
130
+ Returns:
131
+ Template dictionary or None if not found
132
+ """
133
+ global _prompt_templates, _is_initialized
134
+
135
+ if not _is_initialized:
136
+ load_prompt_templates()
137
+
138
+ return _prompt_templates.get(template_name)
139
+
140
+
141
+ def get_all_template_names() -> List[str]:
142
+ """Get a list of all available template names.
143
+
144
+ Returns:
145
+ List of template names
146
+ """
147
+ global _prompt_templates, _is_initialized
148
+
149
+ if not _is_initialized:
150
+ load_prompt_templates()
151
+
152
+ return list(_prompt_templates.keys())
153
+
154
+
155
+ def get_template_metadata() -> List[Dict[str, str]]:
156
+ """Get metadata for all available templates.
157
+
158
+ Returns:
159
+ List of dictionaries with template metadata
160
+ """
161
+ global _prompt_templates, _is_initialized
162
+
163
+ if not _is_initialized:
164
+ load_prompt_templates()
165
+
166
+ return [
167
+ {
168
+ "name": template["name"],
169
+ "title": template["title"],
170
+ "description": template["description"],
171
+ }
172
+ for template in _prompt_templates.values()
173
+ ]
@@ -0,0 +1,109 @@
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
+ """General utility functions for AWS resource operations."""
16
+
17
+ from typing import Any, Dict
18
+
19
+ import boto3
20
+ from botocore.config import Config
21
+ from mcp.server.fastmcp import Context
22
+
23
+ from awslabs.well_architected_security_mcp_server import __version__
24
+
25
+ # User agent configuration for AWS API calls
26
+ USER_AGENT_CONFIG = Config(
27
+ user_agent_extra=f"awslabs/mcp/well-architected-security-mcp-server/{__version__}"
28
+ )
29
+
30
+
31
+ async def list_services_in_region(
32
+ region: str, session: boto3.Session, ctx: Context
33
+ ) -> Dict[str, Any]:
34
+ """List all AWS services being used in a specific region.
35
+
36
+ Args:
37
+ region: AWS region to list services for
38
+ session: boto3 Session for AWS API calls
39
+ ctx: MCP context for error reporting
40
+
41
+ Returns:
42
+ Dictionary with services information and counts
43
+ """
44
+ try:
45
+ # Initialize the result dictionary
46
+ result = {"region": region, "services": [], "service_counts": {}, "total_resources": 0}
47
+
48
+ # Use Resource Explorer to efficiently discover resources
49
+ try:
50
+ resource_explorer = session.client(
51
+ "resource-explorer-2", region_name=region, config=USER_AGENT_CONFIG
52
+ )
53
+
54
+ # Check if Resource Explorer is available in this region
55
+ try:
56
+ # Try to search with Resource Explorer
57
+ resource_explorer.search(
58
+ QueryString="*",
59
+ MaxResults=1, # Just checking if it works
60
+ )
61
+ except Exception as e:
62
+ if "Resource Explorer has not been set up" in str(e):
63
+ await ctx.warning(
64
+ f"Resource Explorer not set up in {region}. Using alternative method."
65
+ )
66
+ return {"region": region, "services": [], "error": str(e)}
67
+ else:
68
+ raise e
69
+
70
+ # Resource Explorer is available, use it to get all resources
71
+ paginator = resource_explorer.get_paginator("search")
72
+ page_iterator = paginator.paginate(QueryString="*", MaxResults=1000)
73
+
74
+ # Track unique services
75
+ services_set = set()
76
+ service_resource_counts = {}
77
+
78
+ # Process each page of results
79
+ for page in page_iterator:
80
+ for resource in page.get("Resources", []):
81
+ # Extract service from ARN
82
+ arn = resource.get("Arn", "")
83
+ if arn:
84
+ arn_parts = arn.split(":")
85
+ if len(arn_parts) >= 3:
86
+ service = arn_parts[2]
87
+ services_set.add(service)
88
+
89
+ # Update count for this service
90
+ if service in service_resource_counts:
91
+ service_resource_counts[service] += 1
92
+ else:
93
+ service_resource_counts[service] = 1
94
+
95
+ # Update result with discovered services
96
+ result["services"] = sorted(list(services_set))
97
+ result["service_counts"] = service_resource_counts
98
+ result["total_resources"] = sum(service_resource_counts.values())
99
+
100
+ except Exception as e:
101
+ await ctx.warning(f"Error using Resource Explorer in {region}: {e}")
102
+ # Fall back to alternative method
103
+ return {"region": region, "services": [], "error": str(e)}
104
+
105
+ return result
106
+
107
+ except Exception as e:
108
+ await ctx.error(f"Error listing services in region {region}: {e}")
109
+ return {"region": region, "services": [], "error": str(e)}