awslabs.ccapi-mcp-server 1.0.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.

Potentially problematic release.


This version of awslabs.ccapi-mcp-server might be problematic. Click here for more details.

Files changed (28) hide show
  1. awslabs/__init__.py +16 -0
  2. awslabs/ccapi_mcp_server/__init__.py +17 -0
  3. awslabs/ccapi_mcp_server/aws_client.py +62 -0
  4. awslabs/ccapi_mcp_server/cloud_control_utils.py +120 -0
  5. awslabs/ccapi_mcp_server/context.py +37 -0
  6. awslabs/ccapi_mcp_server/errors.py +67 -0
  7. awslabs/ccapi_mcp_server/iac_generator.py +203 -0
  8. awslabs/ccapi_mcp_server/impl/__init__.py +13 -0
  9. awslabs/ccapi_mcp_server/impl/tools/__init__.py +13 -0
  10. awslabs/ccapi_mcp_server/impl/tools/explanation.py +325 -0
  11. awslabs/ccapi_mcp_server/impl/tools/infrastructure_generation.py +70 -0
  12. awslabs/ccapi_mcp_server/impl/tools/resource_operations.py +367 -0
  13. awslabs/ccapi_mcp_server/impl/tools/security_scanning.py +223 -0
  14. awslabs/ccapi_mcp_server/impl/tools/session_management.py +221 -0
  15. awslabs/ccapi_mcp_server/impl/utils/__init__.py +13 -0
  16. awslabs/ccapi_mcp_server/impl/utils/validation.py +64 -0
  17. awslabs/ccapi_mcp_server/infrastructure_generator.py +160 -0
  18. awslabs/ccapi_mcp_server/models/__init__.py +13 -0
  19. awslabs/ccapi_mcp_server/models/models.py +118 -0
  20. awslabs/ccapi_mcp_server/schema_manager.py +219 -0
  21. awslabs/ccapi_mcp_server/server.py +733 -0
  22. awslabs/ccapi_mcp_server/static/__init__.py +13 -0
  23. awslabs_ccapi_mcp_server-1.0.1.dist-info/METADATA +656 -0
  24. awslabs_ccapi_mcp_server-1.0.1.dist-info/RECORD +28 -0
  25. awslabs_ccapi_mcp_server-1.0.1.dist-info/WHEEL +4 -0
  26. awslabs_ccapi_mcp_server-1.0.1.dist-info/entry_points.txt +2 -0
  27. awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/LICENSE +175 -0
  28. awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,219 @@
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
+ import json
16
+ import os
17
+ from awslabs.ccapi_mcp_server.aws_client import get_aws_client
18
+ from awslabs.ccapi_mcp_server.errors import ClientError
19
+ from datetime import datetime, timedelta
20
+ from pathlib import Path
21
+ from typing import Dict
22
+
23
+
24
+ # all schema metadata is stored in .schemas/schema_metadata.json. The schemas themselves are all stored in the directory.
25
+ SCHEMA_CACHE_DIR = '.schemas'
26
+ SCHEMA_METADATA_FILE = 'schema_metadata.json'
27
+ SCHEMA_UPDATE_INTERVAL = timedelta(days=7) # Check for updates weekly
28
+
29
+
30
+ class SchemaManager:
31
+ """Responsible for keeping track of schemas, cacheing them locally, and updating them if they are outdated."""
32
+
33
+ def __init__(self):
34
+ """Initialize the schema manager with the cache directory."""
35
+ cache_dir = os.path.join(os.path.dirname(__file__), '.schemas')
36
+ self.cache_dir = Path(cache_dir)
37
+ self.metadata_file = self.cache_dir / SCHEMA_METADATA_FILE
38
+ self.schema_registry: Dict[str, dict] = {}
39
+
40
+ # Ensure cache directory exists
41
+ self.cache_dir.mkdir(exist_ok=True)
42
+
43
+ # Load metadata if it exists
44
+ self.metadata = self._load_metadata()
45
+
46
+ # Load cached schemas into registry
47
+ self._load_cached_schemas()
48
+
49
+ def _load_metadata(self) -> dict:
50
+ """Load schema metadata from file or create if it doesn't exist."""
51
+ if self.metadata_file.exists():
52
+ try:
53
+ with open(self.metadata_file, 'r') as f:
54
+ return json.load(f)
55
+ except json.JSONDecodeError:
56
+ print('Corrupted metadata file. Creating new one.')
57
+
58
+ # Default metadata
59
+ metadata = {'version': '1', 'schemas': {}}
60
+
61
+ # Save default metadata
62
+ with open(self.metadata_file, 'w') as f:
63
+ json.dump(metadata, f, indent=2)
64
+
65
+ return metadata
66
+
67
+ def _load_cached_schemas(self):
68
+ """Load all cached schemas into the registry."""
69
+ for schema_file in self.cache_dir.glob('*.json'):
70
+ if schema_file.name == SCHEMA_METADATA_FILE:
71
+ continue
72
+
73
+ try:
74
+ with open(schema_file, 'r') as f:
75
+ schema = json.load(f)
76
+ if 'typeName' in schema:
77
+ resource_type = schema['typeName']
78
+ self.schema_registry[resource_type] = schema
79
+ print(f'Loaded schema for {resource_type} from cache')
80
+ except (json.JSONDecodeError, IOError) as e:
81
+ print(f'Error loading schema from {schema_file}: {str(e)}')
82
+
83
+ async def get_schema(self, resource_type: str, region: str | None = None) -> dict:
84
+ """Get schema for a resource type, downloading it if necessary."""
85
+ # Check if schema is in registry
86
+ if resource_type in self.schema_registry:
87
+ cached_schema = self.schema_registry[resource_type]
88
+
89
+ # If cached schema is corrupted (empty properties), force reload
90
+ if not cached_schema.get('properties'):
91
+ print(
92
+ f'Cached schema for {resource_type} is corrupted (empty properties), reloading...'
93
+ )
94
+ # Remove from registry to force reload
95
+ del self.schema_registry[resource_type]
96
+ else:
97
+ # Check if schema needs to be updated based on last update time
98
+ if resource_type in self.metadata['schemas']:
99
+ schema_metadata = self.metadata['schemas'][resource_type]
100
+ last_updated_str = schema_metadata.get('last_updated')
101
+
102
+ if last_updated_str:
103
+ try:
104
+ last_updated = datetime.fromisoformat(last_updated_str)
105
+ if datetime.now() - last_updated < SCHEMA_UPDATE_INTERVAL:
106
+ # Schema is recent enough and valid, use cached version
107
+ return cached_schema
108
+ else:
109
+ print(
110
+ f'Schema for {resource_type} is older than {SCHEMA_UPDATE_INTERVAL.days} days, refreshing...'
111
+ )
112
+ except ValueError:
113
+ print(
114
+ f'Invalid timestamp format for {resource_type}: {last_updated_str}'
115
+ )
116
+ else:
117
+ # No metadata for this schema but it's valid, use cached version
118
+ return cached_schema
119
+
120
+ # Download schema (either not cached, expired, or corrupted)
121
+ schema = await self._download_resource_schema(resource_type, region)
122
+ return schema
123
+
124
+ async def _download_resource_schema(
125
+ self, resource_type: str, region: str | None = None
126
+ ) -> dict:
127
+ """Download schema for a specific resource type.
128
+
129
+ Args:
130
+ resource_type: The AWS resource type (e.g., "AWS::S3::Bucket")
131
+ region: AWS region to use for API calls
132
+
133
+ Returns:
134
+ The downloaded schema or None if download failed
135
+ """
136
+ # Extract service name from resource type
137
+ parts = resource_type.split('::')
138
+ if len(parts) < 2:
139
+ raise ClientError(
140
+ f"Invalid resource type format: {resource_type}. Expected format like 'Namespace::Service::Resource'"
141
+ )
142
+
143
+ # If no local spec file or it failed to load, try CloudFormation API
144
+ # Retry logic for schema download
145
+ max_retries = 3
146
+ for attempt in range(max_retries):
147
+ try:
148
+ print(
149
+ f'Downloading schema for {resource_type} using CloudFormation API (attempt {attempt + 1}/{max_retries})'
150
+ )
151
+ cfn_client = get_aws_client('cloudformation', region)
152
+ resp = cfn_client.describe_type(Type='RESOURCE', TypeName=resource_type)
153
+ schema_str = resp['Schema']
154
+
155
+ if not schema_str or len(schema_str) < 100: # Basic sanity check
156
+ raise ClientError(f'Schema response too short: {len(schema_str)} characters')
157
+
158
+ spec = json.loads(schema_str)
159
+
160
+ # Validate that the schema has properties (not empty)
161
+ if not spec.get('properties'):
162
+ raise ClientError(
163
+ f'Downloaded schema for {resource_type} has no properties - API may have failed'
164
+ )
165
+
166
+ # For known taggable resources, verify Tags property exists
167
+ if resource_type in [
168
+ 'AWS::S3::Bucket',
169
+ 'AWS::EC2::Instance',
170
+ 'AWS::RDS::DBInstance',
171
+ ]:
172
+ if 'Tags' not in spec.get('properties', {}):
173
+ print(
174
+ f'Warning: {resource_type} schema missing Tags property, but resource should support tagging'
175
+ )
176
+
177
+ # Save schema to cache only if it's valid
178
+ schema_file = self.cache_dir / f'{resource_type.replace("::", "_")}.json'
179
+ with open(schema_file, 'w') as f:
180
+ f.write(schema_str)
181
+
182
+ # Update registry with the valid schema
183
+ self.schema_registry[resource_type] = spec
184
+
185
+ # Update metadata
186
+ self.metadata['schemas'][resource_type] = {
187
+ 'last_updated': datetime.now().isoformat(),
188
+ 'file_path': str(schema_file),
189
+ 'source': 'cloudformation_api',
190
+ }
191
+
192
+ with open(self.metadata_file, 'w') as f:
193
+ json.dump(self.metadata, f, indent=2)
194
+
195
+ print(f'Processed and cached schema for {resource_type}')
196
+ return spec
197
+
198
+ except Exception as e:
199
+ print(f'Schema download attempt {attempt + 1} failed: {str(e)}')
200
+ if attempt == max_retries - 1: # Last attempt
201
+ raise ClientError(
202
+ f'Failed to download valid schema for {resource_type} after {max_retries} attempts: {str(e)}'
203
+ )
204
+ # Wait before retry
205
+ import time
206
+
207
+ time.sleep(1)
208
+
209
+ # Should never reach here
210
+ raise ClientError(f'Failed to download schema for {resource_type}')
211
+
212
+
213
+ _schema_manager_instance = SchemaManager()
214
+
215
+
216
+ # used to load a single instance of the schema manager
217
+ def schema_manager() -> SchemaManager:
218
+ """Loads a singleton of the resource."""
219
+ return _schema_manager_instance