awslabs.terraform-mcp-server 1.0.14__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.
Files changed (30) hide show
  1. awslabs/__init__.py +17 -0
  2. awslabs/terraform_mcp_server/__init__.py +17 -0
  3. awslabs/terraform_mcp_server/impl/resources/__init__.py +25 -0
  4. awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +66 -0
  5. awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +69 -0
  6. awslabs/terraform_mcp_server/impl/tools/__init__.py +33 -0
  7. awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +223 -0
  8. awslabs/terraform_mcp_server/impl/tools/execute_terragrunt_command.py +320 -0
  9. awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +376 -0
  10. awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +691 -0
  11. awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +641 -0
  12. awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +458 -0
  13. awslabs/terraform_mcp_server/impl/tools/search_user_provided_module.py +349 -0
  14. awslabs/terraform_mcp_server/impl/tools/utils.py +572 -0
  15. awslabs/terraform_mcp_server/models/__init__.py +49 -0
  16. awslabs/terraform_mcp_server/models/models.py +381 -0
  17. awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +1240 -0
  18. awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +1039 -0
  19. awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +143 -0
  20. awslabs/terraform_mcp_server/server.py +440 -0
  21. awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md +3125 -0
  22. awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md +3833 -0
  23. awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md +2523 -0
  24. awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md +142 -0
  25. awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +330 -0
  26. awslabs/terraform_mcp_server/static/__init__.py +38 -0
  27. awslabs_terraform_mcp_server-1.0.14.dist-info/METADATA +166 -0
  28. awslabs_terraform_mcp_server-1.0.14.dist-info/RECORD +30 -0
  29. awslabs_terraform_mcp_server-1.0.14.dist-info/WHEEL +4 -0
  30. awslabs_terraform_mcp_server-1.0.14.dist-info/entry_points.txt +2 -0
awslabs/__init__.py ADDED
@@ -0,0 +1,17 @@
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
+ # This file is part of the awslabs namespace.
16
+ # It is intentionally minimal to support PEP 420 namespace packages.
17
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
@@ -0,0 +1,17 @@
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
+ """awslabs.terraform-mcp-server"""
16
+
17
+ __version__ = '1.0.14'
@@ -0,0 +1,25 @@
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
+ """Resource implementations for the Terraform expert."""
16
+
17
+ from .terraform_aws_provider_resources_listing import terraform_aws_provider_assets_listing_impl
18
+ from .terraform_awscc_provider_resources_listing import (
19
+ terraform_awscc_provider_resources_listing_impl,
20
+ )
21
+
22
+ __all__ = [
23
+ 'terraform_aws_provider_assets_listing_impl',
24
+ 'terraform_awscc_provider_resources_listing_impl',
25
+ ]
@@ -0,0 +1,66 @@
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
+ """Implementation for terraform_aws_provider_resources_listing resource."""
16
+
17
+ import sys
18
+ from loguru import logger
19
+ from pathlib import Path
20
+
21
+
22
+ # Configure logger for enhanced diagnostics with stacktraces
23
+ logger.configure(
24
+ handlers=[
25
+ {
26
+ 'sink': sys.stderr,
27
+ 'backtrace': True,
28
+ 'diagnose': True,
29
+ 'format': '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>',
30
+ }
31
+ ]
32
+ )
33
+
34
+ # Path to the static markdown file
35
+ STATIC_RESOURCES_PATH = (
36
+ Path(__file__).parent.parent.parent / 'static' / 'AWS_PROVIDER_RESOURCES.md'
37
+ )
38
+
39
+
40
+ async def terraform_aws_provider_assets_listing_impl() -> str:
41
+ """Generate a comprehensive listing of AWS provider resources and data sources.
42
+
43
+ This implementation reads from a pre-generated static markdown file instead of
44
+ scraping the web in real-time. The static file should be generated using the
45
+ generate_aws_provider_resources.py script.
46
+
47
+ Returns:
48
+ A markdown formatted string with categorized resources and data sources
49
+ """
50
+ logger.info('Loading AWS provider resources listing from static file')
51
+
52
+ try:
53
+ # Check if the static file exists
54
+ if STATIC_RESOURCES_PATH.exists():
55
+ # Read the static file content
56
+ with open(STATIC_RESOURCES_PATH, 'r', encoding='utf-8') as f:
57
+ content = f.read()
58
+ logger.info('Successfully loaded AWS Provider asset list')
59
+ return content
60
+ else:
61
+ # Send error if static file does not exist
62
+ logger.debug(f"Static assets list file not found at '{STATIC_RESOURCES_PATH}'")
63
+ raise Exception('Static assets list file not found')
64
+ except Exception as e:
65
+ logger.error(f'Error generating AWS provider assets listing: {e}')
66
+ return f'# AWS Provider Assets Listing\n\nError generating listing: {str(e)}'
@@ -0,0 +1,69 @@
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
+ """Implementation for terraform_awscc_provider_resources_listing resource."""
16
+
17
+ import sys
18
+ from loguru import logger
19
+ from pathlib import Path
20
+
21
+
22
+ # Configure logger for enhanced diagnostics with stacktraces
23
+ logger.configure(
24
+ handlers=[
25
+ {
26
+ 'sink': sys.stderr,
27
+ 'backtrace': True,
28
+ 'diagnose': True,
29
+ 'format': '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>',
30
+ }
31
+ ]
32
+ )
33
+
34
+ # Path to the static markdown file
35
+ STATIC_RESOURCES_PATH = (
36
+ Path(__file__).parent.parent.parent / 'static' / 'AWSCC_PROVIDER_RESOURCES.md'
37
+ )
38
+
39
+
40
+ async def terraform_awscc_provider_resources_listing_impl() -> str:
41
+ """Generate a comprehensive listing of AWSCC provider resources and data sources.
42
+
43
+ This implementation reads from a pre-generated static markdown file instead of
44
+ scraping the web in real-time. The static file should be generated using the
45
+ generate_awscc_provider_resources.py script.
46
+
47
+ Returns:
48
+ A markdown formatted string with categorized resources and data sources
49
+ """
50
+ logger.info('Loading AWSCC provider resources listing from static file')
51
+
52
+ try:
53
+ # Check if the static file exists
54
+ if STATIC_RESOURCES_PATH.exists():
55
+ # Read the static file content
56
+ with open(STATIC_RESOURCES_PATH, 'r', encoding='utf-8') as f:
57
+ content = f.read()
58
+
59
+ logger.info(
60
+ f'Successfully loaded AWSCC provider resources from {STATIC_RESOURCES_PATH}'
61
+ )
62
+ return content
63
+ else:
64
+ # Send error if static file does not exist
65
+ logger.debug(f"Static assets list file not found at '{STATIC_RESOURCES_PATH}'")
66
+ raise Exception('Static assets list file not found')
67
+ except Exception as e:
68
+ logger.error(f'Error generating AWSCC provider resources listing: {e}')
69
+ return f'# AWSCC Provider Resources Listing\n\nError generating listing: {str(e)}'
@@ -0,0 +1,33 @@
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
+ """Tool implementations for the terraform MCP server."""
16
+
17
+ from .search_user_provided_module import search_user_provided_module_impl
18
+ from .execute_terraform_command import execute_terraform_command_impl
19
+ from .execute_terragrunt_command import execute_terragrunt_command_impl
20
+ from .search_aws_provider_docs import search_aws_provider_docs_impl
21
+ from .search_awscc_provider_docs import search_awscc_provider_docs_impl
22
+ from .search_specific_aws_ia_modules import search_specific_aws_ia_modules_impl
23
+ from .run_checkov_scan import run_checkov_scan_impl
24
+
25
+ __all__ = [
26
+ 'search_user_provided_module_impl',
27
+ 'execute_terraform_command_impl',
28
+ 'execute_terragrunt_command_impl',
29
+ 'search_aws_provider_docs_impl',
30
+ 'search_awscc_provider_docs_impl',
31
+ 'search_specific_aws_ia_modules_impl',
32
+ 'run_checkov_scan_impl',
33
+ ]
@@ -0,0 +1,223 @@
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
+ """Implementation of Terraform command execution tool."""
16
+
17
+ import json
18
+ import os
19
+ import re
20
+ import subprocess
21
+ from awslabs.terraform_mcp_server.impl.tools.utils import get_dangerous_patterns
22
+ from awslabs.terraform_mcp_server.models import TerraformExecutionRequest, TerraformExecutionResult
23
+ from loguru import logger
24
+
25
+
26
+ async def execute_terraform_command_impl(
27
+ request: TerraformExecutionRequest,
28
+ ) -> TerraformExecutionResult:
29
+ """Execute Terraform workflow commands against an AWS account.
30
+
31
+ This tool runs Terraform commands (init, plan, validate, apply, destroy) in the
32
+ specified working directory, with optional variables and region settings.
33
+
34
+ Parameters:
35
+ request: Details about the Terraform command to execute
36
+
37
+ Returns:
38
+ A TerraformExecutionResult object containing command output and status
39
+ """
40
+ logger.info(f"Executing 'terraform {request.command}' in {request.working_directory}")
41
+
42
+ # Helper function to clean output text
43
+ def clean_output_text(text: str) -> str:
44
+ """Clean output text by removing or replacing problematic Unicode characters.
45
+
46
+ Args:
47
+ text: The text to clean
48
+
49
+ Returns:
50
+ Cleaned text with ASCII-friendly replacements
51
+ """
52
+ if not text:
53
+ return text
54
+
55
+ # First remove ANSI escape sequences (color codes, cursor movement)
56
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
57
+ text = ansi_escape.sub('', text)
58
+
59
+ # Remove C0 and C1 control characters (except common whitespace)
60
+ control_chars = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]')
61
+ text = control_chars.sub('', text)
62
+
63
+ # Replace HTML entities
64
+ html_entities = {
65
+ '-&gt;': '->', # Replace HTML arrow
66
+ '&lt;': '<', # Less than
67
+ '&gt;': '>', # Greater than
68
+ '&amp;': '&', # Ampersand
69
+ }
70
+ for entity, replacement in html_entities.items():
71
+ text = text.replace(entity, replacement)
72
+
73
+ # Replace box-drawing and other special Unicode characters with ASCII equivalents
74
+ unicode_chars = {
75
+ '\u2500': '-', # Horizontal line
76
+ '\u2502': '|', # Vertical line
77
+ '\u2514': '+', # Up and right
78
+ '\u2518': '+', # Up and left
79
+ '\u2551': '|', # Double vertical
80
+ '\u2550': '-', # Double horizontal
81
+ '\u2554': '+', # Double down and right
82
+ '\u2557': '+', # Double down and left
83
+ '\u255a': '+', # Double up and right
84
+ '\u255d': '+', # Double up and left
85
+ '\u256c': '+', # Double cross
86
+ '\u2588': '#', # Full block
87
+ '\u25cf': '*', # Black circle
88
+ '\u2574': '-', # Left box drawing
89
+ '\u2576': '-', # Right box drawing
90
+ '\u2577': '|', # Down box drawing
91
+ '\u2575': '|', # Up box drawing
92
+ }
93
+ for char, replacement in unicode_chars.items():
94
+ text = text.replace(char, replacement)
95
+
96
+ return text
97
+
98
+ # Set environment variables for AWS region if provided
99
+ env = os.environ.copy()
100
+ if request.aws_region:
101
+ env['AWS_REGION'] = request.aws_region
102
+
103
+ # Security check for command injection
104
+ allowed_commands = ['init', 'plan', 'validate', 'apply', 'destroy']
105
+ if request.command not in allowed_commands:
106
+ logger.error(f'Invalid Terraform command: {request.command}')
107
+ return TerraformExecutionResult(
108
+ command=f'terraform {request.command}',
109
+ status='error',
110
+ error_message=f'Invalid Terraform command: {request.command}. Allowed commands are: {", ".join(allowed_commands)}',
111
+ working_directory=request.working_directory,
112
+ outputs=None,
113
+ )
114
+
115
+ # Check for potentially dangerous characters or command injection attempts
116
+ dangerous_patterns = get_dangerous_patterns()
117
+ logger.debug(f'Checking for {len(dangerous_patterns)} dangerous patterns')
118
+
119
+ for pattern in dangerous_patterns:
120
+ if request.variables:
121
+ # Check if the pattern is in any of the variable values
122
+ for var_name, var_value in request.variables.items():
123
+ if pattern in str(var_value) or pattern in str(var_name):
124
+ logger.error(
125
+ f'Potentially dangerous pattern detected in variable {var_name}: {pattern}'
126
+ )
127
+ return TerraformExecutionResult(
128
+ command=f'terraform {request.command}',
129
+ status='error',
130
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in variable '{var_name}'",
131
+ working_directory=request.working_directory,
132
+ outputs=None,
133
+ )
134
+
135
+ # Build the command
136
+ cmd = ['terraform', request.command]
137
+
138
+ # Add auto-approve flag for apply and destroy commands to make them non-interactive
139
+ if request.command in ['apply', 'destroy']:
140
+ logger.info(f'Adding -auto-approve flag to {request.command} command')
141
+ cmd.append('-auto-approve')
142
+
143
+ # Add variables only for commands that accept them (plan, apply, destroy)
144
+ if request.command in ['plan', 'apply', 'destroy'] and request.variables:
145
+ logger.info(f'Adding {len(request.variables)} variables to {request.command} command')
146
+ for key, value in request.variables.items():
147
+ cmd.append(f'-var={key}={value}')
148
+
149
+ # Execute command
150
+ try:
151
+ # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
152
+ # Safe: Command is validated against allowlist, variables are checked for dangerous patterns,
153
+ # working_directory is user-controlled but subprocess uses cwd parameter (not shell injection)
154
+ process = subprocess.run( # noqa: B603 - Safe: allowlisted commands, validated variables, no shell injection
155
+ cmd, cwd=request.working_directory, capture_output=True, text=True, env=env
156
+ )
157
+
158
+ # Prepare the result
159
+ stdout = process.stdout
160
+ stderr = process.stderr if process.stderr else ''
161
+
162
+ # Clean output text if requested
163
+ if request.strip_ansi:
164
+ logger.debug('Cleaning command output text (ANSI codes and control characters)')
165
+ stdout = clean_output_text(stdout)
166
+ stderr = clean_output_text(stderr)
167
+
168
+ result = {
169
+ 'command': f'terraform {request.command}',
170
+ 'status': 'success' if process.returncode == 0 else 'error',
171
+ 'return_code': process.returncode,
172
+ 'stdout': stdout,
173
+ 'stderr': stderr,
174
+ 'working_directory': request.working_directory,
175
+ 'outputs': None,
176
+ }
177
+
178
+ # Get outputs if this was a successful apply command
179
+ if request.command == 'apply' and process.returncode == 0:
180
+ try:
181
+ logger.info('Getting Terraform outputs')
182
+ output_process = subprocess.run( # noqa: B603 - Safe: hardcoded terraform output command with no user input
183
+ ['terraform', 'output', '-json'],
184
+ cwd=request.working_directory,
185
+ capture_output=True,
186
+ text=True,
187
+ env=env,
188
+ )
189
+
190
+ if output_process.returncode == 0 and output_process.stdout:
191
+ # Get output and clean it if needed
192
+ output_stdout = output_process.stdout
193
+ if request.strip_ansi:
194
+ output_stdout = clean_output_text(output_stdout)
195
+
196
+ # Parse the JSON output
197
+ raw_outputs = json.loads(output_stdout)
198
+
199
+ # Process outputs to extract values from complex structure
200
+ processed_outputs = {}
201
+ for key, value in raw_outputs.items():
202
+ # Terraform outputs in JSON format have a nested structure
203
+ # with 'value', 'type', and sometimes 'sensitive'
204
+ if isinstance(value, dict) and 'value' in value:
205
+ processed_outputs[key] = value['value']
206
+ else:
207
+ processed_outputs[key] = value
208
+
209
+ result['outputs'] = processed_outputs
210
+ logger.info(f'Extracted {len(processed_outputs)} Terraform outputs')
211
+ except Exception as e:
212
+ logger.warning(f'Failed to get Terraform outputs: {e}')
213
+
214
+ # Return the output
215
+ return TerraformExecutionResult(**result)
216
+ except Exception as e:
217
+ return TerraformExecutionResult(
218
+ command=f'terraform {request.command}',
219
+ status='error',
220
+ error_message=str(e),
221
+ working_directory=request.working_directory,
222
+ outputs=None,
223
+ )