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.
- awslabs/__init__.py +16 -0
- awslabs/ccapi_mcp_server/__init__.py +17 -0
- awslabs/ccapi_mcp_server/aws_client.py +62 -0
- awslabs/ccapi_mcp_server/cloud_control_utils.py +120 -0
- awslabs/ccapi_mcp_server/context.py +37 -0
- awslabs/ccapi_mcp_server/errors.py +67 -0
- awslabs/ccapi_mcp_server/iac_generator.py +203 -0
- awslabs/ccapi_mcp_server/impl/__init__.py +13 -0
- awslabs/ccapi_mcp_server/impl/tools/__init__.py +13 -0
- awslabs/ccapi_mcp_server/impl/tools/explanation.py +325 -0
- awslabs/ccapi_mcp_server/impl/tools/infrastructure_generation.py +70 -0
- awslabs/ccapi_mcp_server/impl/tools/resource_operations.py +367 -0
- awslabs/ccapi_mcp_server/impl/tools/security_scanning.py +223 -0
- awslabs/ccapi_mcp_server/impl/tools/session_management.py +221 -0
- awslabs/ccapi_mcp_server/impl/utils/__init__.py +13 -0
- awslabs/ccapi_mcp_server/impl/utils/validation.py +64 -0
- awslabs/ccapi_mcp_server/infrastructure_generator.py +160 -0
- awslabs/ccapi_mcp_server/models/__init__.py +13 -0
- awslabs/ccapi_mcp_server/models/models.py +118 -0
- awslabs/ccapi_mcp_server/schema_manager.py +219 -0
- awslabs/ccapi_mcp_server/server.py +733 -0
- awslabs/ccapi_mcp_server/static/__init__.py +13 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/METADATA +656 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/RECORD +28 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/WHEEL +4 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/entry_points.txt +2 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/LICENSE +175 -0
- awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
"""Explanation functionality implementation for CCAPI MCP server."""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import uuid
|
|
19
|
+
from awslabs.ccapi_mcp_server.errors import ClientError
|
|
20
|
+
from awslabs.ccapi_mcp_server.impl.utils.validation import ensure_string
|
|
21
|
+
from awslabs.ccapi_mcp_server.models.models import ExplainRequest
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _format_value(value: Any) -> str:
|
|
26
|
+
"""Format any value for display."""
|
|
27
|
+
if isinstance(value, str):
|
|
28
|
+
return f'"{value[:100]}"' + ('...' if len(value) > 100 else '')
|
|
29
|
+
elif isinstance(value, (int, float, bool)):
|
|
30
|
+
return str(value)
|
|
31
|
+
elif isinstance(value, dict):
|
|
32
|
+
return f'{{dict with {len(value)} keys}}'
|
|
33
|
+
elif isinstance(value, list):
|
|
34
|
+
return f'[list with {len(value)} items]'
|
|
35
|
+
else:
|
|
36
|
+
return f'{type(value).__name__} object'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _generate_explanation(
|
|
40
|
+
content: Any, context: str, operation: str, format: str, user_intent: str
|
|
41
|
+
) -> str:
|
|
42
|
+
"""Generate comprehensive explanation for any type of content."""
|
|
43
|
+
content_type = type(content).__name__
|
|
44
|
+
|
|
45
|
+
# Build header
|
|
46
|
+
if context:
|
|
47
|
+
header = (
|
|
48
|
+
f'## {context} - {operation.title()} Operation'
|
|
49
|
+
if operation != 'analyze'
|
|
50
|
+
else f'## {context} Analysis'
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
header = f'## Data Analysis ({content_type})'
|
|
54
|
+
|
|
55
|
+
if user_intent:
|
|
56
|
+
header += f'\n\n**User Intent:** {user_intent}'
|
|
57
|
+
|
|
58
|
+
explanation = header + '\n\n'
|
|
59
|
+
|
|
60
|
+
# Handle different content types
|
|
61
|
+
if isinstance(content, dict):
|
|
62
|
+
# Check if this is security scan data
|
|
63
|
+
if content.get('scan_status') in ['PASSED', 'FAILED']:
|
|
64
|
+
explanation += _explain_security_scan(content)
|
|
65
|
+
else:
|
|
66
|
+
explanation += _explain_dict(content, format)
|
|
67
|
+
elif isinstance(content, list):
|
|
68
|
+
explanation += _explain_list(content, format)
|
|
69
|
+
elif isinstance(content, str):
|
|
70
|
+
explanation += f'**Content:** {content[:500]}{"..." if len(content) > 500 else ""}'
|
|
71
|
+
elif isinstance(content, (int, float, bool)):
|
|
72
|
+
explanation += f'**Value:** {content} ({content_type})'
|
|
73
|
+
else:
|
|
74
|
+
explanation += f'**Content Type:** {content_type}\n**Value:** {str(content)[:500]}'
|
|
75
|
+
|
|
76
|
+
# Add operation-specific notes
|
|
77
|
+
if operation in ['create', 'update', 'delete']:
|
|
78
|
+
explanation += '\n\n**Infrastructure Operation Notes:**'
|
|
79
|
+
explanation += '\n• This operation will modify AWS resources'
|
|
80
|
+
explanation += '\n• Default management tags will be applied for tracking'
|
|
81
|
+
explanation += '\n• Changes will be applied to the specified AWS region'
|
|
82
|
+
|
|
83
|
+
return explanation
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _explain_dict(data: dict, format: str) -> str:
|
|
87
|
+
"""Explain dictionary content comprehensively with improved formatting."""
|
|
88
|
+
property_names = [key for key in data.keys() if not key.startswith('_')]
|
|
89
|
+
explanation = f'### 📋 Configuration Summary: {len(property_names)} properties\n'
|
|
90
|
+
explanation += f'**Properties:** {", ".join(f"`{name}`" for name in property_names)}\n\n'
|
|
91
|
+
|
|
92
|
+
for key, value in data.items():
|
|
93
|
+
if key.startswith('_'):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
if key == 'Tags' and isinstance(value, list):
|
|
97
|
+
# Special handling for AWS tags
|
|
98
|
+
explanation += f'**🏷️ {key}:** ({len(value)} tags)\n'
|
|
99
|
+
default_tags = []
|
|
100
|
+
user_tags = []
|
|
101
|
+
|
|
102
|
+
for tag in value:
|
|
103
|
+
if isinstance(tag, dict):
|
|
104
|
+
tag_key = tag.get('Key', '')
|
|
105
|
+
tag_value = tag.get('Value', '')
|
|
106
|
+
if tag_key in ['MANAGED_BY', 'MCP_SERVER_SOURCE_CODE', 'MCP_SERVER_VERSION']:
|
|
107
|
+
default_tags.append(f' 🔧 {tag_key}: `{tag_value}` (Default)')
|
|
108
|
+
else:
|
|
109
|
+
user_tags.append(f' ✨ {tag_key}: `{tag_value}`')
|
|
110
|
+
|
|
111
|
+
if user_tags:
|
|
112
|
+
explanation += '\n'.join(user_tags) + '\n'
|
|
113
|
+
if default_tags:
|
|
114
|
+
explanation += '\n'.join(default_tags) + '\n'
|
|
115
|
+
|
|
116
|
+
elif isinstance(value, dict):
|
|
117
|
+
explanation += f'**📄 {key}:** ({len(value)} properties)\n'
|
|
118
|
+
if format == 'detailed':
|
|
119
|
+
for sub_key, sub_value in list(value.items())[:5]:
|
|
120
|
+
if isinstance(sub_value, list) and sub_key == 'Statement':
|
|
121
|
+
# Special handling for policy statements
|
|
122
|
+
explanation += f' • **{sub_key}:** ({len(sub_value)} statements)\n'
|
|
123
|
+
for i, stmt in enumerate(sub_value[:3]):
|
|
124
|
+
if isinstance(stmt, dict):
|
|
125
|
+
sid = stmt.get('Sid', f'Statement {i + 1}')
|
|
126
|
+
effect = stmt.get('Effect', 'Unknown')
|
|
127
|
+
action = stmt.get('Action', 'Unknown')
|
|
128
|
+
principal = stmt.get('Principal', 'Unknown')
|
|
129
|
+
emoji = '✅' if effect == 'Allow' else '❌'
|
|
130
|
+
|
|
131
|
+
# Format principal nicely
|
|
132
|
+
if isinstance(principal, dict):
|
|
133
|
+
if 'AWS' in principal:
|
|
134
|
+
principal_str = f'AWS: {principal["AWS"]}'
|
|
135
|
+
elif 'Service' in principal:
|
|
136
|
+
principal_str = f'Service: {principal["Service"]}'
|
|
137
|
+
else:
|
|
138
|
+
principal_str = str(principal)
|
|
139
|
+
else:
|
|
140
|
+
principal_str = str(principal)
|
|
141
|
+
|
|
142
|
+
# Format action nicely
|
|
143
|
+
if isinstance(action, list):
|
|
144
|
+
action_str = f'{len(action)} actions: {", ".join(action[:3])}'
|
|
145
|
+
if len(action) > 3:
|
|
146
|
+
action_str += f' + {len(action) - 3} more'
|
|
147
|
+
else:
|
|
148
|
+
action_str = str(action)
|
|
149
|
+
|
|
150
|
+
explanation += f' {emoji} **{sid}**: {effect} {action_str} for {principal_str}\n'
|
|
151
|
+
if len(sub_value) > 3:
|
|
152
|
+
explanation += f' ... and {len(sub_value) - 3} more statements\n'
|
|
153
|
+
else:
|
|
154
|
+
explanation += f' • **{sub_key}:** {_format_value(sub_value)}\n'
|
|
155
|
+
if len(value) > 5:
|
|
156
|
+
explanation += f' • ... and {len(value) - 5} more properties\n'
|
|
157
|
+
|
|
158
|
+
elif isinstance(value, list):
|
|
159
|
+
explanation += f'**📝 {key}:** ({len(value)} items)\n'
|
|
160
|
+
if format == 'detailed' and value:
|
|
161
|
+
for i, item in enumerate(value[:3]):
|
|
162
|
+
explanation += f' • **Item {i + 1}:** {_format_value(item)}\n'
|
|
163
|
+
if len(value) > 3:
|
|
164
|
+
explanation += f' • ... and {len(value) - 3} more items\n'
|
|
165
|
+
|
|
166
|
+
else:
|
|
167
|
+
explanation += f'**⚙️ {key}:** `{_format_value(value)}`\n'
|
|
168
|
+
|
|
169
|
+
explanation += '\n'
|
|
170
|
+
|
|
171
|
+
return explanation
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _explain_list(data: list, format: str) -> str:
|
|
175
|
+
"""Explain list content comprehensively."""
|
|
176
|
+
explanation = f'**List Summary:** {len(data)} items\n\n'
|
|
177
|
+
|
|
178
|
+
if format == 'detailed':
|
|
179
|
+
for i, item in enumerate(data[:10]): # Limit to first 10
|
|
180
|
+
explanation += f'**Item {i + 1}:** {_format_value(item)}\n'
|
|
181
|
+
if len(data) > 10:
|
|
182
|
+
explanation += f'\n... and {len(data) - 10} more items\n'
|
|
183
|
+
else:
|
|
184
|
+
explanation += f'Items: {[type(item).__name__ for item in data[:5]]}\n'
|
|
185
|
+
if len(data) > 5:
|
|
186
|
+
explanation += f'... and {len(data) - 5} more\n'
|
|
187
|
+
|
|
188
|
+
return explanation
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _explain_security_scan(scan_data: dict) -> str:
|
|
192
|
+
"""Format security scan results with emojis and clear structure."""
|
|
193
|
+
explanation = ''
|
|
194
|
+
|
|
195
|
+
failed_checks = scan_data.get('raw_failed_checks', [])
|
|
196
|
+
passed_checks = scan_data.get('raw_passed_checks', [])
|
|
197
|
+
scan_status = scan_data.get('scan_status', 'UNKNOWN')
|
|
198
|
+
|
|
199
|
+
# Status summary
|
|
200
|
+
if scan_status == 'PASSED':
|
|
201
|
+
explanation += '✅ **Security Scan: PASSED**\n\n'
|
|
202
|
+
explanation += f'🛡️ **Passed:** {len(passed_checks)} checks\n'
|
|
203
|
+
else:
|
|
204
|
+
explanation += '❌ **Security Scan: ISSUES FOUND**\n\n'
|
|
205
|
+
explanation += f'✅ **Passed:** {len(passed_checks)} checks\n'
|
|
206
|
+
explanation += f'❌ **Failed:** {len(failed_checks)} checks\n\n'
|
|
207
|
+
|
|
208
|
+
# Failed checks details
|
|
209
|
+
if failed_checks:
|
|
210
|
+
explanation += '### 🚨 Failed Security Checks:\n\n'
|
|
211
|
+
for check in failed_checks:
|
|
212
|
+
check_id = check.get('check_id', 'Unknown')
|
|
213
|
+
check_name = check.get('check_name', 'Unknown check')
|
|
214
|
+
# Try to get description from multiple possible fields
|
|
215
|
+
description = (
|
|
216
|
+
check.get('description')
|
|
217
|
+
or check.get('short_description')
|
|
218
|
+
or check.get('guideline')
|
|
219
|
+
or f'Security check failed: {check_name}'
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
explanation += f'• **{check_id}**: {check_name}\n'
|
|
223
|
+
explanation += f' 📝 **Issue:** {description}\n\n'
|
|
224
|
+
|
|
225
|
+
# Passed checks summary (don't show all details)
|
|
226
|
+
if passed_checks:
|
|
227
|
+
explanation += f'### ✅ Passed Security Checks: {len(passed_checks)}\n\n'
|
|
228
|
+
for check in passed_checks[:3]: # Show first 3
|
|
229
|
+
check_id = check.get('check_id', 'Unknown')
|
|
230
|
+
check_name = check.get('check_name', 'Unknown check')
|
|
231
|
+
explanation += f'• **{check_id}**: {check_name} ✅\n'
|
|
232
|
+
|
|
233
|
+
if len(passed_checks) > 3:
|
|
234
|
+
explanation += f'• ... and {len(passed_checks) - 3} more passed checks\n'
|
|
235
|
+
|
|
236
|
+
return explanation
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def explain_impl(request: ExplainRequest, workflow_store: dict) -> dict:
|
|
240
|
+
"""MANDATORY: Explain any data in clear, human-readable format implementation."""
|
|
241
|
+
explained_token = None
|
|
242
|
+
explanation_content = None
|
|
243
|
+
|
|
244
|
+
# Check if we have valid input
|
|
245
|
+
has_generated_code_token = (
|
|
246
|
+
request.generated_code_token
|
|
247
|
+
and isinstance(request.generated_code_token, str)
|
|
248
|
+
and request.generated_code_token.strip()
|
|
249
|
+
)
|
|
250
|
+
has_content = request.content is not None and not hasattr(request.content, 'annotation')
|
|
251
|
+
|
|
252
|
+
if not has_generated_code_token and not has_content:
|
|
253
|
+
raise ClientError("Either 'content' or 'generated_code_token' must be provided")
|
|
254
|
+
|
|
255
|
+
# Handle infrastructure operations with token workflow
|
|
256
|
+
if has_generated_code_token:
|
|
257
|
+
# Infrastructure operation - consume generated_code_token
|
|
258
|
+
if request.generated_code_token not in workflow_store:
|
|
259
|
+
raise ClientError('Invalid generated code token')
|
|
260
|
+
|
|
261
|
+
workflow_data = workflow_store[request.generated_code_token]
|
|
262
|
+
if workflow_data.get('type') != 'generated_code':
|
|
263
|
+
raise ClientError(
|
|
264
|
+
'Invalid token type: expected generated_code token from generate_infrastructure_code()'
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Use content if provided (LLM wants to explain the full response), otherwise use properties from token
|
|
268
|
+
explanation_content = (
|
|
269
|
+
request.content if has_content else workflow_data['data']['properties']
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Create explained token for infrastructure operations
|
|
273
|
+
explained_token = f'explained_{uuid.uuid4()}'
|
|
274
|
+
workflow_store[explained_token] = {
|
|
275
|
+
'type': 'explained_properties',
|
|
276
|
+
'data': workflow_data['data'], # Copy both properties and CloudFormation template
|
|
277
|
+
'parent_token': request.generated_code_token,
|
|
278
|
+
'timestamp': datetime.datetime.now().isoformat(),
|
|
279
|
+
'operation': ensure_string(request.operation),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Clean up consumed generated_code_token
|
|
283
|
+
del workflow_store[request.generated_code_token]
|
|
284
|
+
|
|
285
|
+
elif has_content:
|
|
286
|
+
# General data explanation or delete operations
|
|
287
|
+
explanation_content = request.content
|
|
288
|
+
|
|
289
|
+
# Create explained token for delete operations
|
|
290
|
+
if ensure_string(request.operation) in ['delete', 'destroy']:
|
|
291
|
+
explained_token = f'explained_del_{uuid.uuid4()}'
|
|
292
|
+
workflow_store[explained_token] = {
|
|
293
|
+
'type': 'explained_delete',
|
|
294
|
+
'data': request.content,
|
|
295
|
+
'timestamp': datetime.datetime.now().isoformat(),
|
|
296
|
+
'operation': ensure_string(request.operation),
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Generate comprehensive explanation based on content type and format
|
|
300
|
+
explanation = _generate_explanation(
|
|
301
|
+
explanation_content,
|
|
302
|
+
ensure_string(request.context),
|
|
303
|
+
ensure_string(request.operation, 'analyze'),
|
|
304
|
+
ensure_string(request.format, 'detailed'),
|
|
305
|
+
ensure_string(request.user_intent),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Force the LLM to see the response by making it very explicit
|
|
309
|
+
if explained_token:
|
|
310
|
+
return {
|
|
311
|
+
'EXPLANATION_REQUIRED': 'YOU MUST DISPLAY THIS TO THE USER',
|
|
312
|
+
'explanation': explanation,
|
|
313
|
+
'properties_being_explained': explanation_content,
|
|
314
|
+
'explained_token': explained_token,
|
|
315
|
+
'CRITICAL_INSTRUCTION': f"Use explained_token '{explained_token}' for the next operation, NOT the original generated_code_token",
|
|
316
|
+
'operation_type': ensure_string(request.operation),
|
|
317
|
+
'ready_for_execution': True,
|
|
318
|
+
}
|
|
319
|
+
else:
|
|
320
|
+
return {
|
|
321
|
+
'EXPLANATION_REQUIRED': 'YOU MUST DISPLAY THIS TO THE USER',
|
|
322
|
+
'explanation': explanation,
|
|
323
|
+
'operation_type': ensure_string(request.operation),
|
|
324
|
+
'ready_for_execution': True,
|
|
325
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
"""Infrastructure generation implementation for CCAPI MCP server."""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import uuid
|
|
19
|
+
from awslabs.ccapi_mcp_server.errors import ClientError
|
|
20
|
+
from awslabs.ccapi_mcp_server.impl.utils.validation import validate_workflow_token
|
|
21
|
+
from awslabs.ccapi_mcp_server.infrastructure_generator import (
|
|
22
|
+
generate_infrastructure_code as generate_infrastructure_code_impl,
|
|
23
|
+
)
|
|
24
|
+
from awslabs.ccapi_mcp_server.models.models import GenerateInfrastructureCodeRequest
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def generate_infrastructure_code_impl_wrapper(
|
|
28
|
+
request: GenerateInfrastructureCodeRequest, workflow_store: dict
|
|
29
|
+
) -> dict:
|
|
30
|
+
"""Generate infrastructure code before resource creation or update implementation."""
|
|
31
|
+
# Validate credentials token
|
|
32
|
+
cred_data = validate_workflow_token(request.credentials_token, 'credentials', workflow_store)
|
|
33
|
+
aws_session_data = cred_data['data']
|
|
34
|
+
if not aws_session_data.get('credentials_valid'):
|
|
35
|
+
raise ClientError('Invalid AWS credentials')
|
|
36
|
+
|
|
37
|
+
# V1: Always add required MCP server identification tags
|
|
38
|
+
# Inform user about default tags and ask if they want additional ones
|
|
39
|
+
|
|
40
|
+
# Generate infrastructure code using the existing implementation
|
|
41
|
+
result = await generate_infrastructure_code_impl(
|
|
42
|
+
resource_type=request.resource_type,
|
|
43
|
+
properties=request.properties,
|
|
44
|
+
identifier=request.identifier,
|
|
45
|
+
patch_document=request.patch_document,
|
|
46
|
+
region=request.region or aws_session_data.get('region') or 'us-east-1',
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Generate a generated code token that enforces using the exact properties and template
|
|
50
|
+
generated_code_token = f'generated_code_{str(uuid.uuid4())}'
|
|
51
|
+
|
|
52
|
+
# Store structured workflow data including both properties and CloudFormation template
|
|
53
|
+
workflow_store[generated_code_token] = {
|
|
54
|
+
'type': 'generated_code',
|
|
55
|
+
'data': {
|
|
56
|
+
'properties': result['properties'],
|
|
57
|
+
'cloudformation_template': result.get('cloudformation_template', result['properties']),
|
|
58
|
+
},
|
|
59
|
+
'parent_token': request.credentials_token,
|
|
60
|
+
'timestamp': datetime.datetime.now().isoformat(),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Keep credentials token for later use in create_resource()
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
'generated_code_token': generated_code_token,
|
|
67
|
+
'message': 'Infrastructure code generated successfully. Use generated_code_token with both explain() and run_checkov().',
|
|
68
|
+
'next_step': 'Use explain() and run_checkov() with generated_code_token, then create_resource() with explained_token.',
|
|
69
|
+
**result, # Include all infrastructure code data for display
|
|
70
|
+
}
|