Skip to content

Commit 46578d9

Browse files
authored
fix: module ID version handling inconsistency across tools (#11)
* docs: add rational on readme * docs: add MCP inspector and remove list of tools from readme * fix: bring consistency around definition of module_id
1 parent 88bc835 commit 46578d9

File tree

8 files changed

+209
-101
lines changed

8 files changed

+209
-101
lines changed

test/test_get_module_details.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,25 @@ class TestGetModuleDetailsValidation:
129129
def test_valid_module_id_parsing(self):
130130
"""Test that valid module IDs are parsed correctly."""
131131
# This will be implemented when we create the actual function
132-
from tim_mcp.tools.details import parse_module_id
132+
from tim_mcp.utils.module_id import parse_module_id
133133

134134
# Test standard format
135135
namespace, name, provider = parse_module_id("terraform-ibm-modules/vpc/ibm")
136136
assert namespace == "terraform-ibm-modules"
137137
assert name == "vpc"
138138
assert provider == "ibm"
139139

140+
# Test with version included
141+
from tim_mcp.utils.module_id import parse_module_id_with_version
142+
namespace, name, provider, version = parse_module_id_with_version("terraform-ibm-modules/vpc/ibm/1.2.3")
143+
assert namespace == "terraform-ibm-modules"
144+
assert name == "vpc"
145+
assert provider == "ibm"
146+
assert version == "1.2.3"
147+
140148
def test_invalid_module_id_format(self):
141149
"""Test validation fails for invalid module ID format."""
142-
from tim_mcp.tools.details import parse_module_id
150+
from tim_mcp.utils.module_id import parse_module_id
143151

144152
with pytest.raises(ValidationError) as exc_info:
145153
parse_module_id("invalid-format")
@@ -149,17 +157,19 @@ def test_invalid_module_id_format(self):
149157

150158
def test_empty_module_id_components(self):
151159
"""Test validation fails for empty components."""
152-
from tim_mcp.tools.details import parse_module_id
160+
from tim_mcp.utils.module_id import parse_module_id
153161

154-
with pytest.raises(ValidationError):
162+
with pytest.raises(ValidationError) as exc_info:
155163
parse_module_id("//")
156164

165+
assert exc_info.value.field == "module_id"
166+
157167
def test_module_id_with_extra_slashes(self):
158-
"""Test validation fails for module ID with extra slashes."""
159-
from tim_mcp.tools.details import parse_module_id
168+
"""Test validation fails for module ID with too many slashes."""
169+
from tim_mcp.utils.module_id import parse_module_id
160170

161171
with pytest.raises(ValidationError):
162-
parse_module_id("namespace/name/provider/extra")
172+
parse_module_id("namespace/name/provider/version/extra")
163173

164174

165175
class TestGetModuleDetailsSuccess:
@@ -221,7 +231,7 @@ async def test_get_module_details_specific_version(
221231
]
222232

223233
request = ModuleDetailsRequest(
224-
module_id="terraform-ibm-modules/vpc/ibm", version="7.4.1"
234+
module_id="terraform-ibm-modules/vpc/ibm/7.4.1"
225235
)
226236

227237
result = await get_module_details_impl(request, config)

test/test_list_content.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pytest
1212

1313
from tim_mcp.config import Config
14-
from tim_mcp.exceptions import GitHubError, ModuleNotFoundError, RateLimitError
14+
from tim_mcp.exceptions import GitHubError, ModuleNotFoundError, RateLimitError, ValidationError
1515
from tim_mcp.tools.list_content import list_content_impl
1616
from tim_mcp.types import ListContentRequest
1717

@@ -119,7 +119,7 @@ async def test_list_content_success(
119119
"""Test successful content listing with version latest."""
120120
# Setup
121121
request = ListContentRequest(
122-
module_id="terraform-ibm-modules/vpc/ibm", version="latest"
122+
module_id="terraform-ibm-modules/vpc/ibm"
123123
)
124124

125125
mock_github_client.get_repository_info.return_value = sample_repo_info
@@ -189,7 +189,7 @@ async def test_list_content_with_specific_version(
189189
"""Test content listing with specific version tag."""
190190
# Setup
191191
request = ListContentRequest(
192-
module_id="terraform-ibm-modules/vpc/ibm", version="v5.1.0"
192+
module_id="terraform-ibm-modules/vpc/ibm/v5.1.0"
193193
)
194194

195195
mock_github_client.get_repository_info.return_value = sample_repo_info
@@ -223,7 +223,7 @@ async def test_list_content_module_not_found(self, mock_config, mock_github_clie
223223
"""Test error handling when module repository is not found."""
224224
# Setup
225225
request = ListContentRequest(
226-
module_id="nonexistent/module/ibm", version="latest"
226+
module_id="nonexistent/module/ibm"
227227
)
228228

229229
mock_github_client.get_repository_info.side_effect = ModuleNotFoundError(
@@ -247,7 +247,7 @@ async def test_list_content_github_api_error(self, mock_config, mock_github_clie
247247
"""Test error handling for GitHub API errors."""
248248
# Setup
249249
request = ListContentRequest(
250-
module_id="terraform-ibm-modules/vpc/ibm", version="latest"
250+
module_id="terraform-ibm-modules/vpc/ibm"
251251
)
252252

253253
mock_github_client.get_repository_info.side_effect = GitHubError(
@@ -273,7 +273,7 @@ async def test_list_content_rate_limit_error(self, mock_config, mock_github_clie
273273
"""Test error handling for GitHub rate limit errors."""
274274
# Setup
275275
request = ListContentRequest(
276-
module_id="terraform-ibm-modules/vpc/ibm", version="latest"
276+
module_id="terraform-ibm-modules/vpc/ibm"
277277
)
278278

279279
mock_github_client.get_repository_tree.side_effect = RateLimitError(
@@ -301,13 +301,13 @@ async def test_list_content_rate_limit_error(self, mock_config, mock_github_clie
301301
async def test_list_content_invalid_module_id(self, mock_config):
302302
"""Test error handling for invalid module ID format."""
303303
# Setup
304-
request = ListContentRequest(module_id="invalid-module-id", version="latest")
304+
request = ListContentRequest(module_id="invalid-module-id")
305305

306306
# Execute & Verify
307-
with pytest.raises(ModuleNotFoundError) as exc_info:
307+
with pytest.raises(ValidationError) as exc_info:
308308
await list_content_impl(request, mock_config)
309309

310-
assert "Invalid module ID format" in str(exc_info.value.details)
310+
assert "Invalid module_id format" in str(exc_info.value)
311311

312312
@pytest.mark.asyncio
313313
async def test_list_content_empty_repository(
@@ -316,7 +316,7 @@ async def test_list_content_empty_repository(
316316
"""Test content listing for empty repository."""
317317
# Setup
318318
request = ListContentRequest(
319-
module_id="terraform-ibm-modules/empty/ibm", version="latest"
319+
module_id="terraform-ibm-modules/empty/ibm"
320320
)
321321

322322
mock_github_client.get_repository_info.return_value = sample_repo_info
@@ -348,7 +348,7 @@ async def test_list_content_readme_parsing_fallback(
348348
"""Test README parsing with fallback when content is not available."""
349349
# Setup
350350
request = ListContentRequest(
351-
module_id="terraform-ibm-modules/vpc/ibm", version="latest"
351+
module_id="terraform-ibm-modules/vpc/ibm"
352352
)
353353

354354
mock_github_client.get_repository_info.return_value = sample_repo_info
@@ -406,7 +406,7 @@ async def test_list_content_path_categorization(
406406
]
407407

408408
request = ListContentRequest(
409-
module_id="terraform-ibm-modules/test/ibm", version="latest"
409+
module_id="terraform-ibm-modules/test/ibm"
410410
)
411411

412412
mock_github_client.get_repository_info.return_value = sample_repo_info

tim_mcp/server.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ async def search_modules(
225225

226226

227227
@mcp.tool()
228-
async def get_module_details(module_id: str, version: str = "latest") -> str:
228+
async def get_module_details(module_id: str) -> str:
229229
"""
230230
Get structured module metadata from Terraform Registry - for understanding module interface when writing NEW terraform.
231231
@@ -247,8 +247,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
247247
- Module dependencies
248248
249249
Args:
250-
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm")
251-
version: Specific version or "latest" (default: "latest")
250+
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm" or "terraform-ibm-modules/vpc/ibm/1.2.3")
252251
253252
Returns:
254253
Plain text with markdown formatted module details including inputs, outputs, and description
@@ -257,7 +256,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
257256

258257
try:
259258
# Validate request
260-
request = ModuleDetailsRequest(module_id=module_id, version=version)
259+
request = ModuleDetailsRequest(module_id=module_id)
261260

262261
# Import here to avoid circular imports
263262
from .tools.details import get_module_details_impl
@@ -282,7 +281,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
282281
log_tool_execution(
283282
logger,
284283
"get_module_details",
285-
{"module_id": module_id, "version": version},
284+
{"module_id": module_id},
286285
duration_ms,
287286
success=False,
288287
error="validation_error",
@@ -294,7 +293,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
294293
log_tool_execution(
295294
logger,
296295
"get_module_details",
297-
{"module_id": module_id, "version": version},
296+
{"module_id": module_id},
298297
duration_ms,
299298
success=False,
300299
)
@@ -305,7 +304,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
305304
log_tool_execution(
306305
logger,
307306
"get_module_details",
308-
{"module_id": module_id, "version": version},
307+
{"module_id": module_id},
309308
duration_ms,
310309
success=False,
311310
error=str(e),
@@ -315,7 +314,7 @@ async def get_module_details(module_id: str, version: str = "latest") -> str:
315314

316315

317316
@mcp.tool()
318-
async def list_content(module_id: str, version: str = "latest") -> str:
317+
async def list_content(module_id: str) -> str:
319318
"""
320319
Discover available examples and repository structure - FIRST step in examples workflow.
321320
@@ -341,8 +340,7 @@ async def list_content(module_id: str, version: str = "latest") -> str:
341340
- Use descriptions to select the single most relevant example
342341
343342
Args:
344-
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm")
345-
version: Git tag/branch to scan (default: "latest")
343+
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm" or "terraform-ibm-modules/vpc/ibm/1.2.3")
346344
347345
Returns:
348346
Plain text with markdown formatted content listing organized by category
@@ -351,7 +349,7 @@ async def list_content(module_id: str, version: str = "latest") -> str:
351349

352350
try:
353351
# Validate request
354-
request = ListContentRequest(module_id=module_id, version=version)
352+
request = ListContentRequest(module_id=module_id)
355353

356354
# Import here to avoid circular imports
357355
from .tools.list_content import list_content_impl
@@ -376,7 +374,7 @@ async def list_content(module_id: str, version: str = "latest") -> str:
376374
log_tool_execution(
377375
logger,
378376
"list_content",
379-
{"module_id": module_id, "version": version},
377+
{"module_id": module_id},
380378
duration_ms,
381379
success=False,
382380
error="validation_error",
@@ -388,7 +386,7 @@ async def list_content(module_id: str, version: str = "latest") -> str:
388386
log_tool_execution(
389387
logger,
390388
"list_content",
391-
{"module_id": module_id, "version": version},
389+
{"module_id": module_id},
392390
duration_ms,
393391
success=False,
394392
)
@@ -399,7 +397,7 @@ async def list_content(module_id: str, version: str = "latest") -> str:
399397
log_tool_execution(
400398
logger,
401399
"list_content",
402-
{"module_id": module_id, "version": version},
400+
{"module_id": module_id},
403401
duration_ms,
404402
success=False,
405403
error=str(e),
@@ -414,7 +412,6 @@ async def get_content(
414412
path: str = "",
415413
include_files: str | list[str] | None = None,
416414
exclude_files: str | list[str] | None = None,
417-
version: str = "latest",
418415
) -> str:
419416
"""
420417
Retrieve source code, examples, solutions from GitHub repositories with glob pattern filtering.
@@ -426,11 +423,10 @@ async def get_content(
426423
- Examples: path="examples/basic", include_files=["*.tf"]
427424
428425
Args:
429-
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm")
426+
module_id: Full module identifier (e.g., "terraform-ibm-modules/vpc/ibm" or "terraform-ibm-modules/vpc/ibm/1.2.3")
430427
path: Specific path: "" (root), "examples/basic", "modules/vpc"
431428
include_files: Glob patterns for files to include (e.g., ["*.tf"], ["*.md"])
432429
exclude_files: Glob patterns for files to exclude (e.g., ["*test*"])
433-
version: Git tag/branch to fetch from (default: "latest")
434430
435431
Returns:
436432
Plain text with markdown formatted content
@@ -452,7 +448,6 @@ async def get_content(
452448
path=path,
453449
include_files=sanitized_include_files,
454450
exclude_files=sanitized_exclude_files,
455-
version=version,
456451
)
457452

458453
# Import here to avoid circular imports
@@ -483,7 +478,6 @@ async def get_content(
483478
"path": path,
484479
"include_files": include_files,
485480
"exclude_files": exclude_files,
486-
"version": version,
487481
},
488482
duration_ms,
489483
success=False,
@@ -501,7 +495,6 @@ async def get_content(
501495
"path": path,
502496
"include_files": include_files,
503497
"exclude_files": exclude_files,
504-
"version": version,
505498
},
506499
duration_ms,
507500
success=False,
@@ -518,7 +511,6 @@ async def get_content(
518511
"path": path,
519512
"include_files": include_files,
520513
"exclude_files": exclude_files,
521-
"version": version,
522514
},
523515
duration_ms,
524516
success=False,

0 commit comments

Comments
 (0)