awslabs.terraform-mcp-server 0.0.10__tar.gz → 0.0.12__tar.gz

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.terraform-mcp-server might be problematic. Click here for more details.

Files changed (54) hide show
  1. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/Dockerfile +2 -2
  2. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/PKG-INFO +7 -1
  3. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/README.md +6 -0
  4. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/__init__.py +2 -0
  5. awslabs_terraform_mcp_server-0.0.12/awslabs/terraform_mcp_server/impl/tools/execute_terragrunt_command.py +303 -0
  6. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/models/__init__.py +4 -0
  7. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/models/models.py +63 -0
  8. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/server.py +58 -0
  9. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md +14 -5
  10. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +150 -24
  11. awslabs_terraform_mcp_server-0.0.12/docker-healthcheck.sh +12 -0
  12. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/pyproject.toml +1 -1
  13. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/run_tests.sh +9 -17
  14. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/README.md +16 -1
  15. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/conftest.py +81 -0
  16. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_command_impl.py +76 -0
  17. awslabs_terraform_mcp_server-0.0.12/tests/test_execute_terragrunt_command.py +493 -0
  18. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_server.py +66 -0
  19. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/uv.lock +1401 -1405
  20. awslabs_terraform_mcp_server-0.0.10/docker-healthcheck.sh +0 -12
  21. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/.gitignore +0 -0
  22. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/.pre-commit-config.yaml +0 -0
  23. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/.python-version +0 -0
  24. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/CHANGELOG.md +0 -0
  25. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/__init__.py +0 -0
  26. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/__init__.py +0 -0
  27. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/resources/__init__.py +0 -0
  28. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +0 -0
  29. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +0 -0
  30. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +0 -0
  31. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +0 -0
  32. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +0 -0
  33. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +0 -0
  34. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +0 -0
  35. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/search_user_provided_module.py +0 -0
  36. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/impl/tools/utils.py +0 -0
  37. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +0 -0
  38. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +0 -0
  39. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +0 -0
  40. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md +0 -0
  41. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md +0 -0
  42. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md +0 -0
  43. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/awslabs/terraform_mcp_server/static/__init__.py +0 -0
  44. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/.gitignore +0 -0
  45. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/__init__.py +0 -0
  46. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_execute_terraform_command.py +0 -0
  47. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_models.py +0 -0
  48. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_parameter_annotations.py +0 -0
  49. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_resources.py +0 -0
  50. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_run_checkov_scan.py +0 -0
  51. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_search_user_provided_module.py +0 -0
  52. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_tool_implementations.py +0 -0
  53. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_utils.py +0 -0
  54. {awslabs_terraform_mcp_server-0.0.10 → awslabs_terraform_mcp_server-0.0.12}/tests/test_utils_additional.py +0 -0
@@ -10,7 +10,7 @@
10
10
  # and limitations under the License.
11
11
 
12
12
  #FROM public.ecr.aws/sam/build-python3.10:1.137.1-20250411084548
13
- FROM public.ecr.aws/sam/build-python3.10@sha256:04cdbe84bec08d17d621192bc3f0a9e4a85a83f2ac99aa9241659dfac0d845ea AS uv
13
+ FROM public.ecr.aws/sam/build-python3.10@sha256:a40f492a0cd8d76557f8a187fc00e49e8864b3cea683e74718ce317790c1ce61 AS uv
14
14
 
15
15
  # Install the project into `/app`
16
16
  WORKDIR /app
@@ -44,7 +44,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
44
44
  # Make the directory just in case it doesn't exist
45
45
  RUN mkdir -p /root/.local
46
46
 
47
- FROM public.ecr.aws/sam/build-python3.10@sha256:04cdbe84bec08d17d621192bc3f0a9e4a85a83f2ac99aa9241659dfac0d845ea
47
+ FROM public.ecr.aws/sam/build-python3.10@sha256:a40f492a0cd8d76557f8a187fc00e49e8864b3cea683e74718ce317790c1ce61
48
48
 
49
49
  # Place executables in the environment at the front of the path and include other binaries
50
50
  ENV PATH="/app/.venv/bin:$PATH:/usr/sbin"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.terraform-mcp-server
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for terraform
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: beautifulsoup4>=4.12.0
@@ -56,6 +56,12 @@ MCP server for Terraform on AWS best practices, infrastructure as code patterns,
56
56
  - Pass variables and specify AWS regions
57
57
  - Get formatted command output for analysis
58
58
 
59
+ - **Terragrunt Workflow Execution** - Run Terragrunt commands directly
60
+ - Initialize, plan, validate, apply, run-all and destroy operations
61
+ - Pass variables and specify AWS regions
62
+ - Configure terragrunt-config and and include/exclude paths flags
63
+ - Get formatted command output for analysis
64
+
59
65
  ## Tools and Resources
60
66
 
61
67
  - **Terraform Development Workflow**: Follow security-focused development process via `terraform://workflow_guide`
@@ -41,6 +41,12 @@ MCP server for Terraform on AWS best practices, infrastructure as code patterns,
41
41
  - Pass variables and specify AWS regions
42
42
  - Get formatted command output for analysis
43
43
 
44
+ - **Terragrunt Workflow Execution** - Run Terragrunt commands directly
45
+ - Initialize, plan, validate, apply, run-all and destroy operations
46
+ - Pass variables and specify AWS regions
47
+ - Configure terragrunt-config and and include/exclude paths flags
48
+ - Get formatted command output for analysis
49
+
44
50
  ## Tools and Resources
45
51
 
46
52
  - **Terraform Development Workflow**: Follow security-focused development process via `terraform://workflow_guide`
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .search_user_provided_module import search_user_provided_module_impl
4
4
  from .execute_terraform_command import execute_terraform_command_impl
5
+ from .execute_terragrunt_command import execute_terragrunt_command_impl
5
6
  from .search_aws_provider_docs import search_aws_provider_docs_impl
6
7
  from .search_awscc_provider_docs import search_awscc_provider_docs_impl
7
8
  from .search_specific_aws_ia_modules import search_specific_aws_ia_modules_impl
@@ -10,6 +11,7 @@ from .run_checkov_scan import run_checkov_scan_impl
10
11
  __all__ = [
11
12
  'search_user_provided_module_impl',
12
13
  'execute_terraform_command_impl',
14
+ 'execute_terragrunt_command_impl',
13
15
  'search_aws_provider_docs_impl',
14
16
  'search_awscc_provider_docs_impl',
15
17
  'search_specific_aws_ia_modules_impl',
@@ -0,0 +1,303 @@
1
+ """Implementation of Terragrunt command execution tool."""
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ from awslabs.terraform_mcp_server.impl.tools.utils import get_dangerous_patterns
8
+ from awslabs.terraform_mcp_server.models import (
9
+ TerragruntExecutionRequest,
10
+ TerragruntExecutionResult,
11
+ )
12
+ from loguru import logger
13
+
14
+
15
+ async def execute_terragrunt_command_impl(
16
+ request: TerragruntExecutionRequest,
17
+ ) -> TerragruntExecutionResult:
18
+ """Execute Terragrunt workflow commands against an AWS account.
19
+
20
+ This tool runs Terragrunt commands (init, plan, validate, apply, destroy, run-all) in the
21
+ specified working directory, with optional variables and region settings.
22
+
23
+ Parameters:
24
+ request: Details about the Terragrunt command to execute
25
+
26
+ Returns:
27
+ A TerragruntExecutionResult object containing command output and status
28
+ """
29
+ logger.info(f"Executing 'terragrunt {request.command}' in {request.working_directory}")
30
+
31
+ # Helper function to clean output text
32
+ def clean_output_text(text: str) -> str:
33
+ """Clean output text by removing or replacing problematic Unicode characters.
34
+
35
+ Args:
36
+ text: The text to clean
37
+
38
+ Returns:
39
+ Cleaned text with ASCII-friendly replacements
40
+ """
41
+ if not text:
42
+ return text
43
+
44
+ # First remove ANSI escape sequences (color codes, cursor movement)
45
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
46
+ text = ansi_escape.sub('', text)
47
+
48
+ # Remove C0 and C1 control characters (except common whitespace)
49
+ control_chars = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]')
50
+ text = control_chars.sub('', text)
51
+
52
+ # Replace HTML entities
53
+ html_entities = {
54
+ '->': '->', # Replace HTML arrow
55
+ '&lt;': '<', # Less than
56
+ '&gt;': '>', # Greater than
57
+ '&amp;': '&', # Ampersand
58
+ }
59
+ for entity, replacement in html_entities.items():
60
+ text = text.replace(entity, replacement)
61
+
62
+ # Replace box-drawing and other special Unicode characters with ASCII equivalents
63
+ unicode_chars = {
64
+ '\u2500': '-', # Horizontal line
65
+ '\u2502': '|', # Vertical line
66
+ '\u2514': '+', # Up and right
67
+ '\u2518': '+', # Up and left
68
+ '\u2551': '|', # Double vertical
69
+ '\u2550': '-', # Double horizontal
70
+ '\u2554': '+', # Double down and right
71
+ '\u2557': '+', # Double down and left
72
+ '\u255a': '+', # Double up and right
73
+ '\u255d': '+', # Double up and left
74
+ '\u256c': '+', # Double cross
75
+ '\u2588': '#', # Full block
76
+ '\u25cf': '*', # Black circle
77
+ '\u2574': '-', # Left box drawing
78
+ '\u2576': '-', # Right box drawing
79
+ '\u2577': '|', # Down box drawing
80
+ '\u2575': '|', # Up box drawing
81
+ }
82
+ for char, replacement in unicode_chars.items():
83
+ text = text.replace(char, replacement)
84
+
85
+ return text
86
+
87
+ # Set environment variables for AWS region if provided
88
+ env = os.environ.copy()
89
+ if request.aws_region:
90
+ env['AWS_REGION'] = request.aws_region
91
+
92
+ # Security check for command injection
93
+ allowed_commands = ['init', 'plan', 'validate', 'apply', 'destroy', 'output', 'run-all']
94
+ if request.command not in allowed_commands:
95
+ logger.error(f'Invalid Terragrunt command: {request.command}')
96
+ return TerragruntExecutionResult(
97
+ command=f'terragrunt {request.command}',
98
+ status='error',
99
+ error_message=f'Invalid Terragrunt command: {request.command}. Allowed commands are: {", ".join(allowed_commands)}',
100
+ working_directory=request.working_directory,
101
+ outputs=None,
102
+ affected_dirs=None,
103
+ )
104
+
105
+ # Validate that terragrunt_config is not used with run-all
106
+ if request.terragrunt_config and request.command == 'run-all':
107
+ logger.error('terragrunt_config cannot be used with run-all command')
108
+ return TerragruntExecutionResult(
109
+ command=f'terragrunt {request.command}',
110
+ status='error',
111
+ error_message='Invalid configuration: --terragrunt-config cannot be used with run-all command',
112
+ working_directory=request.working_directory,
113
+ outputs=None,
114
+ affected_dirs=None,
115
+ )
116
+
117
+ # Check for potentially dangerous characters or command injection attempts
118
+ dangerous_patterns = get_dangerous_patterns()
119
+ logger.debug(f'Checking for {len(dangerous_patterns)} dangerous patterns')
120
+
121
+ for pattern in dangerous_patterns:
122
+ if request.variables:
123
+ # Check if the pattern is in any of the variable values
124
+ for var_name, var_value in request.variables.items():
125
+ if pattern in str(var_value) or pattern in str(var_name):
126
+ logger.error(
127
+ f'Potentially dangerous pattern detected in variable {var_name}: {pattern}'
128
+ )
129
+ return TerragruntExecutionResult(
130
+ command=f'terragrunt {request.command}',
131
+ status='error',
132
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in variable '{var_name}'",
133
+ working_directory=request.working_directory,
134
+ outputs=None,
135
+ affected_dirs=None,
136
+ )
137
+
138
+ # Check terragrunt_config for dangerous patterns
139
+ if request.terragrunt_config and pattern in str(request.terragrunt_config):
140
+ logger.error(f'Potentially dangerous pattern detected in terragrunt_config: {pattern}')
141
+ return TerragruntExecutionResult(
142
+ command=f'terragrunt {request.command}',
143
+ status='error',
144
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in terragrunt_config",
145
+ working_directory=request.working_directory,
146
+ outputs=None,
147
+ affected_dirs=None,
148
+ )
149
+
150
+ # Also check include_dirs and exclude_dirs for dangerous patterns
151
+ if request.include_dirs:
152
+ for dir_path in request.include_dirs:
153
+ if pattern in str(dir_path):
154
+ logger.error(
155
+ f'Potentially dangerous pattern detected in include_dirs: {pattern}'
156
+ )
157
+ return TerragruntExecutionResult(
158
+ command=f'terragrunt {request.command}',
159
+ status='error',
160
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in include_dirs",
161
+ working_directory=request.working_directory,
162
+ outputs=None,
163
+ affected_dirs=None,
164
+ )
165
+
166
+ if request.exclude_dirs:
167
+ for dir_path in request.exclude_dirs:
168
+ if pattern in str(dir_path):
169
+ logger.error(
170
+ f'Potentially dangerous pattern detected in exclude_dirs: {pattern}'
171
+ )
172
+ return TerragruntExecutionResult(
173
+ command=f'terragrunt {request.command}',
174
+ status='error',
175
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in exclude_dirs",
176
+ working_directory=request.working_directory,
177
+ outputs=None,
178
+ affected_dirs=None,
179
+ )
180
+
181
+ # Build the command
182
+ base_cmd = ['terragrunt']
183
+
184
+ # Handle run-all command differently
185
+ if request.command == 'run-all':
186
+ base_cmd.append('run-all')
187
+ # The actual terraform command becomes the first argument
188
+ # Default to 'apply' if not specified in the command
189
+ base_cmd.append('apply')
190
+ else:
191
+ base_cmd.append(request.command)
192
+
193
+ # Add auto-approve flag for apply and destroy commands to make them non-interactive
194
+ if request.command in ['apply', 'destroy'] or (request.command == 'run-all'):
195
+ logger.info(f'Adding -auto-approve flag to {request.command} command')
196
+ base_cmd.append('-auto-approve')
197
+
198
+ # Add terragrunt_config if specified and not using run-all
199
+ if request.terragrunt_config:
200
+ logger.info(f'Using custom terragrunt config file: {request.terragrunt_config}')
201
+ base_cmd.append(f'--terragrunt-config={request.terragrunt_config}')
202
+
203
+ # Add variables only for commands that accept them (plan, apply, destroy, output)
204
+ if request.command in ['plan', 'apply', 'destroy', 'output', 'run-all'] and request.variables:
205
+ logger.info(f'Adding {len(request.variables)} variables to {request.command} command')
206
+ for key, value in request.variables.items():
207
+ base_cmd.append(f'-var={key}={value}')
208
+
209
+ # Add include-dirs if specified
210
+ if request.include_dirs and request.command == 'run-all':
211
+ for dir_path in request.include_dirs:
212
+ base_cmd.append(f'--queue-include-dir={dir_path}')
213
+
214
+ # Add exclude-dirs if specified
215
+ if request.exclude_dirs and request.command == 'run-all':
216
+ for dir_path in request.exclude_dirs:
217
+ base_cmd.append(f'--queue-exclude-dir={dir_path}')
218
+
219
+ # Execute command
220
+ try:
221
+ process = subprocess.run(
222
+ base_cmd, cwd=request.working_directory, capture_output=True, text=True, env=env
223
+ )
224
+
225
+ # Prepare the result
226
+ stdout = process.stdout
227
+ stderr = process.stderr if process.stderr else ''
228
+
229
+ # Clean output text if requested
230
+ if request.strip_ansi:
231
+ logger.debug('Cleaning command output text (ANSI codes and control characters)')
232
+ stdout = clean_output_text(stdout)
233
+ stderr = clean_output_text(stderr)
234
+
235
+ # Extract affected directories for run-all command
236
+ affected_dirs = None
237
+ if request.command == 'run-all':
238
+ affected_dirs = []
239
+ # Look for directory paths in the output
240
+ dir_pattern = re.compile(r'Module at\s+"([^"]+)"')
241
+ for match in dir_pattern.finditer(stdout):
242
+ affected_dirs.append(match.group(1))
243
+
244
+ result = {
245
+ 'command': f'terragrunt {request.command}',
246
+ 'status': 'success' if process.returncode == 0 else 'error',
247
+ 'return_code': process.returncode,
248
+ 'stdout': stdout,
249
+ 'stderr': stderr,
250
+ 'working_directory': request.working_directory,
251
+ 'outputs': None,
252
+ 'affected_dirs': affected_dirs,
253
+ }
254
+
255
+ # Get outputs if this was a successful apply or output command
256
+ if (
257
+ request.command in ['apply', 'output'] or (request.command == 'run-all')
258
+ ) and process.returncode == 0:
259
+ try:
260
+ logger.info('Getting Terragrunt outputs')
261
+ output_process = subprocess.run(
262
+ ['terragrunt', 'output', '-json'],
263
+ cwd=request.working_directory,
264
+ capture_output=True,
265
+ text=True,
266
+ env=env,
267
+ )
268
+
269
+ if output_process.returncode == 0 and output_process.stdout:
270
+ # Get output and clean it if needed
271
+ output_stdout = output_process.stdout
272
+ if request.strip_ansi:
273
+ output_stdout = clean_output_text(output_stdout)
274
+
275
+ # Parse the JSON output
276
+ raw_outputs = json.loads(output_stdout)
277
+
278
+ # Process outputs to extract values from complex structure
279
+ processed_outputs = {}
280
+ for key, value in raw_outputs.items():
281
+ # Terraform outputs in JSON format have a nested structure
282
+ # with 'value', 'type', and sometimes 'sensitive'
283
+ if isinstance(value, dict) and 'value' in value:
284
+ processed_outputs[key] = value['value']
285
+ else:
286
+ processed_outputs[key] = value
287
+
288
+ result['outputs'] = processed_outputs
289
+ logger.info(f'Extracted {len(processed_outputs)} Terragrunt outputs')
290
+ except Exception as e:
291
+ logger.warning(f'Failed to get Terragrunt outputs: {e}')
292
+
293
+ # Return the output
294
+ return TerragruntExecutionResult(**result)
295
+ except Exception as e:
296
+ return TerragruntExecutionResult(
297
+ command=f'terragrunt {request.command}',
298
+ status='error',
299
+ error_message=str(e),
300
+ working_directory=request.working_directory,
301
+ outputs=None,
302
+ affected_dirs=None,
303
+ )
@@ -5,6 +5,8 @@ from .models import (
5
5
  SubmoduleInfo,
6
6
  TerraformExecutionRequest,
7
7
  TerraformExecutionResult,
8
+ TerragruntExecutionRequest,
9
+ TerragruntExecutionResult,
8
10
  CheckovVulnerability,
9
11
  CheckovScanRequest,
10
12
  CheckovScanResult,
@@ -21,6 +23,8 @@ __all__ = [
21
23
  'SubmoduleInfo',
22
24
  'TerraformExecutionRequest',
23
25
  'TerraformExecutionResult',
26
+ 'TerragruntExecutionRequest',
27
+ 'TerragruntExecutionResult',
24
28
  'CheckovVulnerability',
25
29
  'CheckovScanRequest',
26
30
  'CheckovScanResult',
@@ -302,3 +302,66 @@ class SearchUserProvidedModuleResult(BaseModel):
302
302
  outputs: List[TerraformOutput] = Field([], description='Outputs provided by the module')
303
303
  readme_content: Optional[str] = Field(None, description='README content of the module')
304
304
  error_message: Optional[str] = Field(None, description='Error message if execution failed')
305
+
306
+
307
+ class TerragruntExecutionRequest(BaseModel):
308
+ """Request model for Terragrunt command execution with parameters.
309
+
310
+ Attributes:
311
+ command: The Terragrunt command to execute (init, plan, validate, apply, destroy, etc.).
312
+ working_directory: Directory containing Terragrunt configuration files.
313
+ variables: Optional dictionary of Terraform variables to pass.
314
+ aws_region: Optional AWS region to use.
315
+ strip_ansi: Whether to strip ANSI color codes from command output.
316
+ include_dirs: Optional list of directories to include in a multi-module run.
317
+ exclude_dirs: Optional list of directories to exclude from a multi-module run.
318
+ run_all: Whether to run the command in all subdirectories with terragrunt.hcl files.
319
+ """
320
+
321
+ command: Literal['init', 'plan', 'validate', 'apply', 'destroy', 'output', 'run-all'] = Field(
322
+ ..., description='Terragrunt command to execute'
323
+ )
324
+ working_directory: str = Field(..., description='Directory containing Terragrunt files')
325
+ variables: Optional[Dict[str, str]] = Field(None, description='Terraform variables to pass')
326
+ aws_region: Optional[str] = Field(None, description='AWS region to use')
327
+ strip_ansi: bool = Field(True, description='Whether to strip ANSI color codes from output')
328
+ include_dirs: Optional[List[str]] = Field(
329
+ None, description='Directories to include in a multi-module run'
330
+ )
331
+ exclude_dirs: Optional[List[str]] = Field(
332
+ None, description='Directories to exclude from a multi-module run'
333
+ )
334
+ run_all: bool = Field(False, description='Run command on all modules in subdirectories')
335
+ terragrunt_config: Optional[str] = Field(
336
+ None, description='Path to a custom terragrunt config file (not valid with run-all)'
337
+ )
338
+
339
+
340
+ class TerragruntExecutionResult(BaseModel):
341
+ """Result model for Terragrunt command execution.
342
+
343
+ Attributes:
344
+ command: The Terragrunt command that was executed.
345
+ status: Execution status (success/error).
346
+ return_code: The command's return code (0 for success).
347
+ stdout: Standard output from the Terragrunt command.
348
+ stderr: Standard error output from the Terragrunt command.
349
+ working_directory: Directory where the command was executed.
350
+ error_message: Optional error message if execution failed.
351
+ outputs: Dictionary of output values from Terragrunt (for apply command).
352
+ affected_dirs: List of directories affected by a run-all command.
353
+ """
354
+
355
+ command: str
356
+ status: Literal['success', 'error']
357
+ return_code: Optional[int] = None
358
+ stdout: Optional[str] = None
359
+ stderr: str = ''
360
+ working_directory: str
361
+ error_message: Optional[str] = None
362
+ outputs: Optional[Dict[str, Any]] = Field(
363
+ None, description='Terragrunt outputs (for apply or output command)'
364
+ )
365
+ affected_dirs: Optional[List[str]] = Field(
366
+ None, description='Directories affected by a run-all command'
367
+ )
@@ -8,6 +8,7 @@ from awslabs.terraform_mcp_server.impl.resources import (
8
8
  )
9
9
  from awslabs.terraform_mcp_server.impl.tools import (
10
10
  execute_terraform_command_impl,
11
+ execute_terragrunt_command_impl,
11
12
  run_checkov_scan_impl,
12
13
  search_aws_provider_docs_impl,
13
14
  search_awscc_provider_docs_impl,
@@ -24,6 +25,8 @@ from awslabs.terraform_mcp_server.models import (
24
25
  TerraformAWSProviderDocsResult,
25
26
  TerraformExecutionRequest,
26
27
  TerraformExecutionResult,
28
+ TerragruntExecutionRequest,
29
+ TerragruntExecutionResult,
27
30
  )
28
31
  from awslabs.terraform_mcp_server.static import (
29
32
  AWS_TERRAFORM_BEST_PRACTICES,
@@ -84,6 +87,61 @@ async def execute_terraform_command(
84
87
  return await execute_terraform_command_impl(request)
85
88
 
86
89
 
90
+ @mcp.tool(name='ExecuteTerragruntCommand')
91
+ async def execute_terragrunt_command(
92
+ command: Literal['init', 'plan', 'validate', 'apply', 'destroy', 'output', 'run-all'] = Field(
93
+ ..., description='Terragrunt command to execute'
94
+ ),
95
+ working_directory: str = Field(..., description='Directory containing Terragrunt files'),
96
+ variables: Optional[Dict[str, str]] = Field(None, description='Terraform variables to pass'),
97
+ aws_region: Optional[str] = Field(None, description='AWS region to use'),
98
+ strip_ansi: bool = Field(True, description='Whether to strip ANSI color codes from output'),
99
+ include_dirs: Optional[List[str]] = Field(
100
+ None, description='Directories to include in a multi-module run'
101
+ ),
102
+ exclude_dirs: Optional[List[str]] = Field(
103
+ None, description='Directories to exclude from a multi-module run'
104
+ ),
105
+ run_all: bool = Field(False, description='Run command on all modules in subdirectories'),
106
+ terragrunt_config: Optional[str] = Field(
107
+ None, description='Path to a custom terragrunt config file (not valid with run-all)'
108
+ ),
109
+ ) -> TerragruntExecutionResult:
110
+ """Execute Terragrunt workflow commands against an AWS account.
111
+
112
+ This tool runs Terragrunt commands (init, plan, validate, apply, destroy, run-all) in the
113
+ specified working directory, with optional variables and region settings. Terragrunt extends
114
+ Terraform's functionality by providing features like remote state management, dependencies
115
+ between modules, and the ability to execute Terraform commands on multiple modules at once.
116
+
117
+ Parameters:
118
+ command: Terragrunt command to execute
119
+ working_directory: Directory containing Terragrunt files
120
+ variables: Terraform variables to pass
121
+ aws_region: AWS region to use
122
+ strip_ansi: Whether to strip ANSI color codes from output
123
+ include_dirs: Directories to include in a multi-module run
124
+ exclude_dirs: Directories to exclude from a multi-module run
125
+ run_all: Run command on all modules in subdirectories
126
+ terragrunt_config: Path to a custom terragrunt config file (not valid with run-all)
127
+
128
+ Returns:
129
+ A TerragruntExecutionResult object containing command output and status
130
+ """
131
+ request = TerragruntExecutionRequest(
132
+ command=command,
133
+ working_directory=working_directory,
134
+ variables=variables,
135
+ aws_region=aws_region,
136
+ strip_ansi=strip_ansi,
137
+ include_dirs=include_dirs,
138
+ exclude_dirs=exclude_dirs,
139
+ run_all=run_all,
140
+ terragrunt_config=terragrunt_config,
141
+ )
142
+ return await execute_terragrunt_command_impl(request)
143
+
144
+
87
145
  @mcp.tool(name='SearchAwsProviderDocs')
88
146
  async def search_aws_provider_docs(
89
147
  asset_name: str = Field(
@@ -1,8 +1,6 @@
1
1
  # Terraform MCP Server Instructions
2
2
 
3
- ## Overview
4
-
5
- MCP server specialized in AWS cloud infrastructure provided through Terraform. I help you create, understand, optimize, and execute Terraform configurations for AWS using security-focused development practices.
3
+ MCP server specialized in AWS cloud infrastructure provided through Terraform. I help you create, understand, optimize, and execute Terraform Or Terragrunt configurations for AWS using security-focused development practices.
6
4
 
7
5
  ## How to Use This Server (Required Workflow)
8
6
 
@@ -80,7 +78,10 @@ When implementing specific AWS resources (only after confirming no suitable AWS-
80
78
  1. `ExecuteTerraformCommand`
81
79
  * Execute Terraform commands in the sequence specified by the workflow
82
80
  * Supports: validate, init, plan, apply, destroy
83
- 2. `RunCheckovScan`
81
+ 2. `ExecuteTerragruntCommand`
82
+ * Execute Terragrunt commands in the sequence specified by the workflow
83
+ * Supports: validate, init, plan, apply, destroy, output, run-all
84
+ 3. `RunCheckovScan`
84
85
  * Run after validation passes, before initialization
85
86
  * Identifies security and compliance issues
86
87
 
@@ -103,6 +104,8 @@ The AWSCC provider (Cloud Control API-based) offers:
103
104
  - "Find documentation for awscc_lambda_function resource" (specifically AWSCC)
104
105
  - "Find documentation for aws_lambda_function resource" (specifically AWS)
105
106
  - "Execute terraform plan in my ./infrastructure directory"
107
+ - "Execute terragrunt plan in my ./infrastructure directory"
108
+ - "Execute terragrunt run-all plan in my ./infrastructure directory"
106
109
  - "How can I use the AWS Bedrock module to create a RAG application?"
107
110
  - "Show me details about the AWS-IA Bedrock Terraform module"
108
111
  - "Compare the four specific AWS-IA modules for generative AI applications"
@@ -115,6 +118,10 @@ The AWSCC provider (Cloud Control API-based) offers:
115
118
  - "Use the terraform-aws-modules/vpc/aws module to implement a VPC"
116
119
  - "Search for the hashicorp/consul/aws module and explain how to use it"
117
120
  - "What variables are required for the terraform-aws-modules/eks/aws module?"
121
+ - "I have a multi-environment Terragrunt project. How can I run apply on all modules at once?"
122
+ - "Execute terragrunt run-all apply in my ./infrastructure directory"
123
+ - "How to construct a well-formed terragrunt hierarchy folder structure"
124
+ - "Generate common inputs for all environments using generate in Terragrunt"
118
125
 
119
126
  ## Best Practices
120
127
 
@@ -129,5 +136,7 @@ When interacting with this server:
129
136
  7. **Be specific** about your requirements and constraints
130
137
  8. **Specify AWS region** when relevant to your infrastructure needs
131
138
  9. **Provide context** about your architecture and use case
132
- 10. **For Terraform execution**, ensure the working directory exists and contains valid Terraform files
139
+ 10. **For Terraform/Terragrunt execution**, ensure the working directory exists and contains valid Terraform/Terragrunt files
133
140
  11. **Review generated code** carefully before applying changes to your infrastructure
141
+ 12. When using **Terragrunt**, leverage DRY features—locals, dependencies, and generate blocks—to compose multi-env stacks.
142
+ 13. **Organize repos with clear folder hierarchies** (e.g. live/, modules/) and consistent naming so both Terraform and Terragrunt code is discoverable.