awslabs.terraform-mcp-server 0.0.6__tar.gz → 0.0.7__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 (49) hide show
  1. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/PKG-INFO +7 -1
  2. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/README.md +6 -0
  3. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/__init__.py +2 -0
  4. awslabs_terraform_mcp_server-0.0.7/awslabs/terraform_mcp_server/impl/tools/search_user_provided_module.py +346 -0
  5. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/models/__init__.py +4 -0
  6. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/models/models.py +44 -0
  7. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/server.py +52 -1
  8. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md +7 -0
  9. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +6 -0
  10. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/pyproject.toml +1 -1
  11. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/README.md +16 -0
  12. awslabs_terraform_mcp_server-0.0.7/tests/test_search_user_provided_module.py +965 -0
  13. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_server.py +292 -0
  14. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/uv.lock +1 -1
  15. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/.gitignore +0 -0
  16. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/.pre-commit-config.yaml +0 -0
  17. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/.python-version +0 -0
  18. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/CHANGELOG.md +0 -0
  19. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/__init__.py +0 -0
  20. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/__init__.py +0 -0
  21. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/resources/__init__.py +0 -0
  22. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +0 -0
  23. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +0 -0
  24. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +0 -0
  25. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +0 -0
  26. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +0 -0
  27. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +0 -0
  28. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +0 -0
  29. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/impl/tools/utils.py +0 -0
  30. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +0 -0
  31. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +0 -0
  32. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +0 -0
  33. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md +0 -0
  34. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md +0 -0
  35. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md +0 -0
  36. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/awslabs/terraform_mcp_server/static/__init__.py +0 -0
  37. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/run_tests.sh +0 -0
  38. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/.gitignore +0 -0
  39. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/__init__.py +0 -0
  40. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/conftest.py +0 -0
  41. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_command_impl.py +0 -0
  42. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_execute_terraform_command.py +0 -0
  43. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_models.py +0 -0
  44. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_parameter_annotations.py +0 -0
  45. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_resources.py +0 -0
  46. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_run_checkov_scan.py +0 -0
  47. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_tool_implementations.py +0 -0
  48. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_utils.py +0 -0
  49. {awslabs_terraform_mcp_server-0.0.6 → awslabs_terraform_mcp_server-0.0.7}/tests/test_utils_additional.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.terraform-mcp-server
3
- Version: 0.0.6
3
+ Version: 0.0.7
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
@@ -45,6 +45,12 @@ MCP server for Terraform on AWS best practices, infrastructure as code patterns,
45
45
  - SageMaker endpoint deployment for ML model hosting
46
46
  - Serverless Streamlit application deployment for AI interfaces
47
47
 
48
+ - **Terraform Registry Module Analysis** - Analyze Terraform Registry modules
49
+ - Search for modules by URL or identifier
50
+ - Extract input variables, output variables, and README content
51
+ - Understand module usage and configuration options
52
+ - Analyze module structure and dependencies
53
+
48
54
  - **Terraform Workflow Execution** - Run Terraform commands directly
49
55
  - Initialize, plan, validate, apply, and destroy operations
50
56
  - Pass variables and specify AWS regions
@@ -30,6 +30,12 @@ MCP server for Terraform on AWS best practices, infrastructure as code patterns,
30
30
  - SageMaker endpoint deployment for ML model hosting
31
31
  - Serverless Streamlit application deployment for AI interfaces
32
32
 
33
+ - **Terraform Registry Module Analysis** - Analyze Terraform Registry modules
34
+ - Search for modules by URL or identifier
35
+ - Extract input variables, output variables, and README content
36
+ - Understand module usage and configuration options
37
+ - Analyze module structure and dependencies
38
+
33
39
  - **Terraform Workflow Execution** - Run Terraform commands directly
34
40
  - Initialize, plan, validate, apply, and destroy operations
35
41
  - Pass variables and specify AWS regions
@@ -1,5 +1,6 @@
1
1
  """Tool implementations for the terraform MCP server."""
2
2
 
3
+ from .search_user_provided_module import search_user_provided_module_impl
3
4
  from .execute_terraform_command import execute_terraform_command_impl
4
5
  from .search_aws_provider_docs import search_aws_provider_docs_impl
5
6
  from .search_awscc_provider_docs import search_awscc_provider_docs_impl
@@ -7,6 +8,7 @@ from .search_specific_aws_ia_modules import search_specific_aws_ia_modules_impl
7
8
  from .run_checkov_scan import run_checkov_scan_impl
8
9
 
9
10
  __all__ = [
11
+ 'search_user_provided_module_impl',
10
12
  'execute_terraform_command_impl',
11
13
  'search_aws_provider_docs_impl',
12
14
  'search_awscc_provider_docs_impl',
@@ -0,0 +1,346 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+ """Implementation of user provided module from the Terraform registry search tool."""
12
+
13
+ import re
14
+ import requests
15
+ import traceback
16
+ from awslabs.terraform_mcp_server.impl.tools.utils import (
17
+ clean_description,
18
+ extract_outputs_from_readme,
19
+ get_github_release_details,
20
+ get_variables_tf,
21
+ )
22
+ from awslabs.terraform_mcp_server.models import (
23
+ SearchUserProvidedModuleRequest,
24
+ SearchUserProvidedModuleResult,
25
+ TerraformOutput,
26
+ TerraformVariable,
27
+ )
28
+ from loguru import logger
29
+ from typing import Any, Dict, Optional, Tuple
30
+ from urllib.parse import urlparse
31
+
32
+
33
+ async def search_user_provided_module_impl(
34
+ request: SearchUserProvidedModuleRequest,
35
+ ) -> SearchUserProvidedModuleResult:
36
+ """Analyze a Terraform module from the registry.
37
+
38
+ This tool takes a Terraform registry module URL and analyzes its input variables,
39
+ output variables, README, and other details to provide comprehensive information
40
+ about the module.
41
+
42
+ Parameters:
43
+ request: Details about the Terraform module to analyze
44
+
45
+ Returns:
46
+ A SearchUserProvidedModuleResult object containing module information
47
+ """
48
+ logger.info(f'Analyzing Terraform module: {request.module_url}')
49
+
50
+ try:
51
+ # Parse the module URL to extract namespace, name, and provider
52
+ module_parts = parse_module_url(request.module_url)
53
+ if not module_parts:
54
+ return SearchUserProvidedModuleResult(
55
+ status='error',
56
+ module_name='unknown',
57
+ module_url=request.module_url,
58
+ module_version='unknown',
59
+ module_description='',
60
+ variables=[],
61
+ outputs=[],
62
+ readme_content=None,
63
+ error_message=f'Invalid module URL format: {request.module_url}. Expected format: [namespace]/[name]/[provider] or registry.terraform.io/[namespace]/[name]/[provider]',
64
+ )
65
+
66
+ namespace, name, provider = module_parts
67
+
68
+ # Fetch module details from Terraform Registry
69
+ module_details = await get_module_details(namespace, name, provider, request.version)
70
+ if not module_details:
71
+ return SearchUserProvidedModuleResult(
72
+ status='error',
73
+ module_name=name,
74
+ module_url=request.module_url,
75
+ module_version=request.version or 'latest',
76
+ module_description='',
77
+ variables=[],
78
+ outputs=[],
79
+ readme_content=None,
80
+ error_message=f'Failed to fetch module details from Terraform Registry: {request.module_url}',
81
+ )
82
+
83
+ # Extract module information
84
+ module_version = module_details.get('version', request.version or 'latest')
85
+ module_description = clean_description(module_details.get('description', ''))
86
+ readme_content = module_details.get('readme_content', '')
87
+
88
+ # Get variables and outputs
89
+ variables = []
90
+ outputs = []
91
+
92
+ # Extract variables from module details
93
+ if 'variables' in module_details and module_details['variables']:
94
+ variables = [TerraformVariable(**var_data) for var_data in module_details['variables']]
95
+ elif 'root' in module_details and 'inputs' in module_details['root']:
96
+ # Extract from registry API format
97
+ for var_name, var_data in module_details['root']['inputs'].items():
98
+ variables.append(
99
+ TerraformVariable(
100
+ name=var_name,
101
+ type=var_data.get('type', ''),
102
+ description=var_data.get('description', ''),
103
+ default=var_data.get('default'),
104
+ required=var_data.get('required', True),
105
+ )
106
+ )
107
+
108
+ # Extract outputs from module details
109
+ if 'outputs' in module_details and module_details['outputs']:
110
+ outputs = [
111
+ TerraformOutput(name=output['name'], description=output.get('description', ''))
112
+ for output in module_details['outputs']
113
+ ]
114
+ elif 'root' in module_details and 'outputs' in module_details['root']:
115
+ # Extract from registry API format
116
+ for output_name, output_data in module_details['root']['outputs'].items():
117
+ outputs.append(
118
+ TerraformOutput(
119
+ name=output_name,
120
+ description=output_data.get('description', ''),
121
+ )
122
+ )
123
+ elif readme_content:
124
+ # Try to extract outputs from README
125
+ extracted_outputs = extract_outputs_from_readme(readme_content)
126
+ if extracted_outputs:
127
+ outputs = [
128
+ TerraformOutput(name=output['name'], description=output.get('description', ''))
129
+ for output in extracted_outputs
130
+ ]
131
+
132
+ # Create the result
133
+ result = SearchUserProvidedModuleResult(
134
+ status='success',
135
+ module_name=name,
136
+ module_url=request.module_url,
137
+ module_version=module_version,
138
+ module_description=module_description,
139
+ variables=variables,
140
+ outputs=outputs,
141
+ readme_content=readme_content,
142
+ error_message=None,
143
+ )
144
+
145
+ return result
146
+
147
+ except Exception as e:
148
+ logger.error(f'Error analyzing Terraform module: {e}')
149
+ logger.debug(f'Stack trace: {traceback.format_exc()}')
150
+ return SearchUserProvidedModuleResult(
151
+ status='error',
152
+ module_name=request.module_url.split('/')[-2]
153
+ if '/' in request.module_url
154
+ else 'unknown',
155
+ module_url=request.module_url,
156
+ module_version=request.version or 'latest',
157
+ module_description='',
158
+ variables=[],
159
+ outputs=[],
160
+ readme_content=None,
161
+ error_message=f'Error analyzing Terraform module: {str(e)}',
162
+ )
163
+
164
+
165
+ def parse_module_url(module_url: str) -> Optional[Tuple[str, str, str]]:
166
+ """Parse a Terraform module URL to extract namespace, name, and provider.
167
+
168
+ Args:
169
+ module_url: The module URL or identifier (e.g., "hashicorp/consul/aws" or "registry.terraform.io/hashicorp/consul/aws")
170
+
171
+ Returns:
172
+ Tuple containing (namespace, name, provider) or None if invalid format
173
+ """
174
+ # First, handle registry.terraform.io URLs (with or without scheme)
175
+ parsed_url = None
176
+
177
+ # If URL has a scheme (http://, https://)
178
+ if '://' in module_url:
179
+ parsed_url = urlparse(module_url)
180
+ # For URLs without scheme, add a dummy scheme to enable proper URL parsing
181
+ else:
182
+ parsed_url = urlparse(f'https://{module_url}')
183
+
184
+ # Check if this is a registry.terraform.io URL
185
+ if parsed_url.netloc == 'registry.terraform.io':
186
+ # Extract path and remove leading slash
187
+ path = parsed_url.path.lstrip('/')
188
+ parts = path.split('/')
189
+ else:
190
+ # Simple module path format (namespace/name/provider)
191
+ parts = module_url.split('/')
192
+
193
+ # Ensure we have at least namespace/name/provider
194
+ if len(parts) < 3:
195
+ return None
196
+
197
+ namespace = parts[0]
198
+ name = parts[1]
199
+ provider = parts[2]
200
+
201
+ return namespace, name, provider
202
+
203
+
204
+ async def get_module_details(
205
+ namespace: str, name: str, provider: str, version: Optional[str] = None
206
+ ) -> Dict[str, Any]:
207
+ """Fetch detailed information about a Terraform module from the registry.
208
+
209
+ Args:
210
+ namespace: The module namespace (e.g., hashicorp)
211
+ name: The module name (e.g., consul)
212
+ provider: The provider (e.g., aws)
213
+ version: Optional specific version to fetch
214
+
215
+ Returns:
216
+ Dictionary containing module details
217
+ """
218
+ logger.info(f'Fetching details for module {namespace}/{name}/{provider}')
219
+
220
+ try:
221
+ # Get basic module info via API
222
+ details_url = f'https://registry.terraform.io/v1/modules/{namespace}/{name}/{provider}'
223
+ if version:
224
+ details_url += f'/{version}'
225
+
226
+ logger.debug(f'Making API request to: {details_url}')
227
+
228
+ response = requests.get(details_url)
229
+ response.raise_for_status()
230
+
231
+ details = response.json()
232
+ logger.debug(
233
+ f'Received module details. Status code: {response.status_code}, Content size: {len(response.text)} bytes'
234
+ )
235
+
236
+ # Get the version
237
+ module_version = version or details.get('version', '')
238
+ if not module_version and 'latest' in details and 'version' in details['latest']:
239
+ module_version = details['latest']['version']
240
+
241
+ # Try to get README content and version details
242
+ readme_content = None
243
+ version_details = None
244
+
245
+ # APPROACH 1: Try to see if the registry API provides README content directly
246
+ logger.debug('Checking for README content in API response')
247
+ if 'readme' in details and details['readme']:
248
+ readme_content = details['readme']
249
+ logger.info(
250
+ f'Found README content directly in API response: {len(readme_content)} chars'
251
+ )
252
+
253
+ # APPROACH 2: Try using the GitHub repo URL for README content and version details
254
+ if 'source' in details:
255
+ source_url = details.get('source')
256
+ # Validate GitHub URL using regex
257
+ if isinstance(source_url, str) and re.match(r'https://github.com/', source_url):
258
+ logger.info(f'Found GitHub source URL: {source_url}')
259
+
260
+ # Extract GitHub owner and repo
261
+ github_parts = re.match(r'https://github.com/([^/]+)/([^/]+)', source_url)
262
+ if github_parts:
263
+ owner, repo = github_parts.groups()
264
+ logger.info(f'Extracted GitHub repo: {owner}/{repo}')
265
+
266
+ # Get version details from GitHub
267
+ github_version_info = await get_github_release_details(owner, repo)
268
+ version_details = github_version_info['details']
269
+ version_from_github = github_version_info['version']
270
+
271
+ if version_from_github:
272
+ logger.info(f'Found version from GitHub: {version_from_github}')
273
+ if not module_version:
274
+ module_version = version_from_github
275
+
276
+ # Get variables.tf content and parsed variables
277
+ variables_content, variables = await get_variables_tf(owner, repo, 'main')
278
+ if variables_content and variables:
279
+ logger.info(f'Found variables.tf with {len(variables)} variables')
280
+ details['variables_content'] = variables_content
281
+ details['variables'] = [var.dict() for var in variables]
282
+ else:
283
+ # Try master branch as fallback
284
+ variables_content, variables = await get_variables_tf(
285
+ owner, repo, 'master'
286
+ )
287
+ if variables_content and variables:
288
+ logger.info(
289
+ f'Found variables.tf in master branch with {len(variables)} variables'
290
+ )
291
+ details['variables_content'] = variables_content
292
+ details['variables'] = [var.dict() for var in variables]
293
+
294
+ # If README content not already found, try fetching it from GitHub
295
+ if not readme_content:
296
+ logger.debug(f'Fetching README from GitHub source: {source_url}')
297
+
298
+ # Try main branch first, then fall back to master if needed
299
+ for branch in ['main', 'master']:
300
+ raw_readme_url = f'https://raw.githubusercontent.com/{owner}/{repo}/{branch}/README.md'
301
+ logger.debug(f'Trying to fetch README from: {raw_readme_url}')
302
+
303
+ readme_response = requests.get(raw_readme_url)
304
+ if readme_response.status_code == 200:
305
+ readme_content = readme_response.text
306
+ logger.info(
307
+ f'Successfully fetched README from GitHub ({branch}): {len(readme_content)} chars'
308
+ )
309
+ break
310
+
311
+ # Add readme_content to details if available
312
+ if readme_content:
313
+ logger.info(f'Successfully extracted README content ({len(readme_content)} chars)')
314
+
315
+ # Extract outputs from README content
316
+ outputs = extract_outputs_from_readme(readme_content)
317
+ if outputs:
318
+ logger.info(f'Extracted {len(outputs)} outputs from README')
319
+ details['outputs'] = outputs
320
+
321
+ # Trim if too large
322
+ if len(readme_content) > 8000:
323
+ logger.debug(
324
+ f'README content exceeds 8000 characters ({len(readme_content)}), truncating...'
325
+ )
326
+ readme_content = readme_content[:8000] + '...\n[README truncated due to length]'
327
+ logger.debug('README content truncated')
328
+
329
+ details['readme_content'] = readme_content
330
+ else:
331
+ logger.warning('No README content found through any method')
332
+
333
+ # Add version details if available
334
+ if version_details:
335
+ logger.info('Adding version details to response')
336
+ details['version_details'] = version_details
337
+
338
+ # Add version to details
339
+ details['version'] = module_version
340
+
341
+ return details
342
+
343
+ except Exception as e:
344
+ logger.error(f'Error fetching module details: {e}')
345
+ logger.debug(f'Stack trace: {traceback.format_exc()}')
346
+ return {}
@@ -10,6 +10,8 @@ from .models import (
10
10
  CheckovScanResult,
11
11
  TerraformVariable,
12
12
  TerraformOutput,
13
+ SearchUserProvidedModuleRequest,
14
+ SearchUserProvidedModuleResult,
13
15
  )
14
16
 
15
17
  __all__ = [
@@ -24,4 +26,6 @@ __all__ = [
24
26
  'CheckovScanResult',
25
27
  'TerraformVariable',
26
28
  'TerraformOutput',
29
+ 'SearchUserProvidedModuleRequest',
30
+ 'SearchUserProvidedModuleResult',
27
31
  ]
@@ -258,3 +258,47 @@ class CheckovScanResult(BaseModel):
258
258
  )
259
259
  summary: Dict[str, Any] = Field({}, description='Summary of scan results')
260
260
  raw_output: Optional[str] = Field(None, description='Raw output from Checkov')
261
+
262
+
263
+ class SearchUserProvidedModuleRequest(BaseModel):
264
+ """Request model for searching user-provided Terraform modules.
265
+
266
+ Attributes:
267
+ module_url: URL of the Terraform module in the registry (e.g., 'hashicorp/consul/aws').
268
+ version: Optional specific version of the module to analyze.
269
+ variables: Optional dictionary of variables to use when analyzing the module.
270
+ """
271
+
272
+ module_url: str = Field(
273
+ ..., description='URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")'
274
+ )
275
+ version: Optional[str] = Field(None, description='Specific version of the module to analyze')
276
+ variables: Optional[Dict[str, Any]] = Field(
277
+ None, description='Variables to use when analyzing the module'
278
+ )
279
+
280
+
281
+ class SearchUserProvidedModuleResult(BaseModel):
282
+ """Result model for searching user-provided Terraform modules.
283
+
284
+ Attributes:
285
+ status: Execution status (success/error).
286
+ module_name: Name of the analyzed module.
287
+ module_url: URL of the module in the registry.
288
+ module_version: Version of the module that was analyzed.
289
+ module_description: Description of the module.
290
+ variables: List of variables defined by the module.
291
+ outputs: List of outputs provided by the module.
292
+ readme_content: The README content of the module.
293
+ error_message: Optional error message if execution failed.
294
+ """
295
+
296
+ status: Literal['success', 'error']
297
+ module_name: str
298
+ module_url: str
299
+ module_version: str
300
+ module_description: str
301
+ variables: List[TerraformVariable] = Field([], description='Variables defined by the module')
302
+ outputs: List[TerraformOutput] = Field([], description='Outputs provided by the module')
303
+ readme_content: Optional[str] = Field(None, description='README content of the module')
304
+ error_message: Optional[str] = Field(None, description='Error message if execution failed')
@@ -12,11 +12,14 @@ from awslabs.terraform_mcp_server.impl.tools import (
12
12
  search_aws_provider_docs_impl,
13
13
  search_awscc_provider_docs_impl,
14
14
  search_specific_aws_ia_modules_impl,
15
+ search_user_provided_module_impl,
15
16
  )
16
17
  from awslabs.terraform_mcp_server.models import (
17
18
  CheckovScanRequest,
18
19
  CheckovScanResult,
19
20
  ModuleSearchResult,
21
+ SearchUserProvidedModuleRequest,
22
+ SearchUserProvidedModuleResult,
20
23
  TerraformAWSCCProviderDocsResult,
21
24
  TerraformAWSProviderDocsResult,
22
25
  TerraformExecutionRequest,
@@ -29,7 +32,7 @@ from awslabs.terraform_mcp_server.static import (
29
32
  )
30
33
  from mcp.server.fastmcp import FastMCP
31
34
  from pydantic import Field
32
- from typing import Dict, List, Literal, Optional
35
+ from typing import Any, Dict, List, Literal, Optional
33
36
 
34
37
 
35
38
  mcp = FastMCP(
@@ -264,6 +267,54 @@ async def run_checkov_scan(
264
267
  return await run_checkov_scan_impl(request)
265
268
 
266
269
 
270
+ @mcp.tool(name='SearchUserProvidedModule')
271
+ async def search_user_provided_module(
272
+ module_url: str = Field(
273
+ ..., description='URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")'
274
+ ),
275
+ version: Optional[str] = Field(None, description='Specific version of the module to analyze'),
276
+ variables: Optional[Dict[str, Any]] = Field(
277
+ None, description='Variables to use when analyzing the module'
278
+ ),
279
+ ) -> SearchUserProvidedModuleResult:
280
+ """Search for a user-provided Terraform registry module and understand its inputs, outputs, and usage.
281
+
282
+ This tool takes a Terraform registry module URL and analyzes its input variables,
283
+ output variables, README, and other details to provide comprehensive information
284
+ about the module.
285
+
286
+ The module URL should be in the format "namespace/name/provider" (e.g., "hashicorp/consul/aws")
287
+ or "registry.terraform.io/namespace/name/provider".
288
+
289
+ Examples:
290
+ - To search for the HashiCorp Consul module:
291
+ search_user_provided_module(module_url='hashicorp/consul/aws')
292
+
293
+ - To search for a specific version of a module:
294
+ search_user_provided_module(module_url='terraform-aws-modules/vpc/aws', version='3.14.0')
295
+
296
+ - To search for a module with specific variables:
297
+ search_user_provided_module(
298
+ module_url='terraform-aws-modules/eks/aws',
299
+ variables={'cluster_name': 'my-cluster', 'vpc_id': 'vpc-12345'}
300
+ )
301
+
302
+ Parameters:
303
+ module_url: URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")
304
+ version: Optional specific version of the module to analyze
305
+ variables: Optional dictionary of variables to use when analyzing the module
306
+
307
+ Returns:
308
+ A SearchUserProvidedModuleResult object containing module information
309
+ """
310
+ request = SearchUserProvidedModuleRequest(
311
+ module_url=module_url,
312
+ version=version,
313
+ variables=variables,
314
+ )
315
+ return await search_user_provided_module_impl(request)
316
+
317
+
267
318
  # * Resources
268
319
  @mcp.resource(
269
320
  name='terraform_development_workflow',
@@ -70,6 +70,10 @@ When implementing specific AWS resources (only after confirming no suitable AWS-
70
70
  3. `SearchSpecificAwsIaModules`
71
71
  * Use for specialized AI/ML infrastructure needs
72
72
  * Returns details for supported AWS-IA modules
73
+ 4. `SearchUserProvidedModule`
74
+ * Analyze any Terraform Registry module by URL or identifier
75
+ * Extract input variables, output variables, and README content
76
+ * Understand module usage and configuration options
73
77
 
74
78
  ### Command Execution Tools
75
79
 
@@ -108,6 +112,9 @@ The AWSCC provider (Cloud Control API-based) offers:
108
112
  - "Is this VPC configuration secure? Let's scan it with Checkov."
109
113
  - "Find documentation for awscc_lambda_function to ensure we're using the preferred provider."
110
114
  - "We need a Bedrock implementation for RAG. Let's search for AWS-IA modules that can help."
115
+ - "Use the terraform-aws-modules/vpc/aws module to implement a VPC"
116
+ - "Search for the hashicorp/consul/aws module and explain how to use it"
117
+ - "What variables are required for the terraform-aws-modules/eks/aws module?"
111
118
 
112
119
  ## Best Practices
113
120
 
@@ -83,6 +83,11 @@ flowchart TD
83
83
  * FIRST check for specialized AWS-IA modules (`SearchSpecificAwsIaModules` tool)
84
84
  * If no suitable module exists, THEN use AWSCC provider resources (`SearchAwsccProviderDocs` tool)
85
85
  * ONLY fall back to traditional AWS provider (`SearchAwsProviderDocs` tool) when the above options don't meet requirements
86
+ - When a user provides a specific Terraform Registry module to use:
87
+ * Use the `SearchUserProvidedModule` tool to analyze the module
88
+ * Extract input variables, output variables, and README content
89
+ * Understand module usage and configuration options
90
+ * Provide guidance on how to use the module correctly
86
91
  - MCP Resources and tools to consult:
87
92
  - Resources
88
93
  - *terraform_development_workflow* to consult this guide and to use it to ensure you're following the development workflow correctly
@@ -91,6 +96,7 @@ flowchart TD
91
96
  - *terraform_aws_provider_resources_listing* for available AWS resources
92
97
  - Tools
93
98
  - *SearchSpecificAwsIaModules* tool to check for specialized AWS-IA modules first (Bedrock, OpenSearch Serverless, SageMaker, Streamlit)
99
+ - *SearchUserProvidedModule* tool to analyze any Terraform Registry module provided by the user
94
100
  - *SearchAwsccProviderDocs* tool to look up specific Cloud Control API resources
95
101
  - *SearchAwsProviderDocs* tool to look up specific resource documentation
96
102
  2. Validate Code
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "awslabs.terraform-mcp-server"
3
- version = "0.0.6"
3
+ version = "0.0.7"
4
4
  description = "An AWS Labs Model Context Protocol (MCP) server for terraform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -12,6 +12,7 @@ The tests are organized as follows:
12
12
  - `test_command_impl.py`: Tests for the Terraform command execution implementation
13
13
  - `test_execute_terraform_command.py`: Dedicated tests for the execute_terraform_command implementation
14
14
  - `test_run_checkov_scan.py`: Dedicated tests for the run_checkov_scan implementation
15
+ - `test_search_user_provided_module.py`: Dedicated tests for the search_user_provided_module implementation
15
16
  - `test_resources.py`: Tests for the resource implementations
16
17
  - `test_tool_implementations.py`: Tests for the tool implementations
17
18
  - `test_utils.py` and `test_utils_additional.py`: Tests for utility functions
@@ -157,3 +158,18 @@ Dedicated tests for the run_checkov_scan implementation, including:
157
158
  - Testing security checks for dangerous patterns
158
159
  - Testing CLI output parsing
159
160
  - Testing error handling and exception handling
161
+
162
+ ### test_search_user_provided_module.py
163
+
164
+ Dedicated tests for the search_user_provided_module implementation, including:
165
+
166
+ - Testing the parse_module_url function for different URL formats
167
+ - Testing the get_module_details function with successful responses
168
+ - Testing the get_module_details function with error responses
169
+ - Testing the search_user_provided_module_impl function with successful responses
170
+ - Testing the search_user_provided_module_impl function with registry prefix in URL
171
+ - Testing the search_user_provided_module_impl function with invalid URL
172
+ - Testing the search_user_provided_module_impl function when module is not found
173
+ - Testing the search_user_provided_module_impl function when an exception occurs
174
+ - Testing the extraction of outputs from README when not available in module details
175
+ - Testing the format_json helper function for serializing objects