This project has been archived by its maintainers, and is no longer receiving any updates.
TFModSearch MCP Server
A Model Context Protocol (MCP) server that provides intelligent search capabilities for Terraform AWS module documentation using hybrid search (keyword matching, BM25, and semantic embeddings).
Ready to Use: Includes a pre-built search index with embeddings for 55 curated Terraform AWS modules. Install and run the MCP server immediately—no index building required!
🤔 Why TFModSearch?
When an AI assistant writes Terraform, it often guesses at module names, invents variables that don't exist, or reaches for outdated syntax. TFModSearch gives your assistant a curated, versioned, offline knowledge base of the official terraform-aws-modules so it can:
- Find the right module from intent — "I need a Redis cache" resolves to
elasticache, not a hallucinated module name. - Ground generated code in real inputs/outputs — the assistant pulls the full, current module documentation (submodules, variables, outputs, examples) on demand instead of improvising.
- Stay fast, private, and deterministic — search runs locally on CPU against a pre-built index. No external API calls, no rate limits, no network round-trips.
- Grep any module's live docs when you need to — for a pinpoint variable/default lookup, a module outside the curated AWS catalog, or a specific older version,
grep_module_docsfetches the registry's current docs (cached, version-pinnable) and returns just the matching lines with context.
Think of it as an always-available, searchable reference card for every terraform-aws-modules module — kept accurate and shipped ready to run.
🚀 Features
- Hybrid Search Engine: Combines keyword matching (IDF-weighted), BM25 text relevance, exact module name matching, and semantic similarity for accurate results
- Live Registry Grep:
grep_module_docsregex-searches the full, current docs of any Terraform Registry module (version-pinnable, cached), returning only matching lines with context — pinpoint lookups without dumping 100k-token documents - MCP Integration: Seamlessly integrates with Claude Desktop and other MCP clients
- Fast & Efficient: Pre-built search index with CPU-only inference using
intfloat/e5-small-v2model - Ready to Use: Includes pre-built index (
model/tfmod_e5_small_index.pkl) with embeddings fromintfloat/e5-small-v2model and curated Terraform AWS module documentation - Comprehensive Catalog: Access to terraform-aws-modules documentation compiled from official sources with rich metadata
- Security-First: Built-in path validation and access controls for safe file operations
- Configurable Weights: Fine-tune search scoring through YAML config or CLI arguments
📋 Table of Contents
- Why TFModSearch?
- Installation
- Quick Start
- Usage
- MCP Tools
- Configuration
- Development
- Testing
- Security
- Architecture
- Contributing
- License
📦 Installation
Plugin Install (Claude Code / Codex — Recommended)
The plugin configures the MCP server automatically and adds workflow skills that make the agent search current module documentation before writing Terraform.
Claude Code:
/plugin marketplace add SantyagoSeaman/tfmodsearch
/plugin install tfmod-search@tfmodsearch
Codex CLI:
/plugin marketplace add SantyagoSeaman/tfmodsearch
/plugin install tfmod-search@tfmodsearch
Both bundle:
- The tfmod-search MCP server (runs via
uvx tfmodsearchby default — uv required; the Claude Code plugin can optionally run it via Docker instead, see Docker below) - Eight skills:
aws-terraform-modules— auto-invoked when writing Terraform for AWS: search first, write from current docs, pin versions/tf-module <query>— instant module lookup with a ready-to-paste snippet/tf-grep <module> <pattern>— grep the live registry docs of any module (version-pinnable, non-AWS too) for an exact quote/tf-stack <requirement>— scaffold a multi-module stack with correct output→input wiringtf-migrate— replace hand-writtenaws_*resources with a covering module, verified attribute-by-attributetf-module-upgrade— audit pinned versions and variable usage against current docstf-review— review a diff or PR's module usagetf-troubleshoot— diagnose terraform failures; ships a prefilter script that reduces logs of any size to just the diagnostics
- Two subagents (Claude Code):
tf-log-analystandtf-diff-revieweranalyze large logs and diffs in an isolated context, so your session only sees the findings
Quick Install (Any MCP Client)
The server is on PyPI — no need to clone the repository.
Install uv first (if not already installed):
curl -LsSf https://astral.sh/uv/install.sh | sh
Then add to your MCP client config:
{
"mcpServers": {
"terraform-modules": {
"command": "uvx",
"args": ["tfmodsearch"]
}
}
}
Tip: Run
uvx tfmodsearch --warmuponce after installing — it pre-downloads the embedding model (~130 MB) and verifies the server end-to-end, so the first real query is instant.
Bundled and ready: The pre-built search index and all 55 module docs ship inside the package, so
uvxfetches, installs, and runs the server with nothing to clone or rebuild. (Theintfloat/e5-small-v2embedding model — ~130 MB — is downloaded automatically on the first search to encode your query, then cached for subsequent queries.)
Note: If you get "command not found" error, use the full path to
uvx:# Find uvx location which uvx # Example output: /Users/username/.local/bin/uvxThen use the full path in your config:
"command": "/Users/username/.local/bin/uvx"
🐳 Docker (opt-in)
uvx tfmodsearch stays the documented default. An official Docker image is also published to
GHCR for environments that prefer or require a container — air-gapped/offline setups, CI runners
without a Python/uv toolchain, or teams that standardize MCP server deployment on Docker. The
image bakes in the embedding model, the search index, and the NLTK tokenizer data at build time,
so search_modules, get_module, and modules_list make zero network calls at runtime.
(grep_module_docs is the one tool designed to reach the live Terraform Registry — that still
needs real network; only a --network none warmup, which never calls it, is fully offline.)
Any MCP client, launch the image directly (never add -t/--tty — it corrupts the stdio
JSON-RPC stream):
{
"mcpServers": {
"terraform-modules": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/santyagoseaman/tfmodsearch:0.15.0"]
}
}
}
Claude Code plugin, the bundled launcher defaults to uvx and switches to Docker when
TFMODSEARCH_DOCKER=1 is set (in ~/.claude/settings.json's env block, or exported before
launching Claude Code):
export TFMODSEARCH_DOCKER=1
# optional: pin a different tag
export TFMODSEARCH_IMAGE=ghcr.io/santyagoseaman/tfmodsearch:0.15.0
If Docker is requested but not on PATH, the launcher falls back to uvx with a warning instead
of failing. This dual-mode launcher currently applies to the Claude Code plugin only — the
Codex plugin stays uvx-only (Codex CLI doesn't yet reliably resolve a plugin-relative path in
its mcp.json).
Note: the Claude Code plugin now launches via a bundled
python3launcher script instead of callinguvxdirectly, so apython3interpreter onPATHis a prerequisite for the plugin (in addition touv/uvx) — on macOS/Linux this is normally already present; on Windows, make surepython3(not justpython/thepylauncher) resolves onPATH.
Verify the offline property yourself:
docker run --network none -i --rm ghcr.io/santyagoseaman/tfmodsearch:0.15.0 --warmup
Prerequisites
- Python 3.12 or higher
- uv (recommended) or pip
Local Installation (For Development)
# Clone the repository
git clone https://github.com/SantyagoSeaman/tfmodsearch.git
cd tfmodsearch
# Create virtual environment and install dependencies
uv venv --python 3.12
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install -e .
Using pip
# Clone the repository
git clone https://github.com/SantyagoSeaman/tfmodsearch.git
cd tfmodsearch
# Create virtual environment and install dependencies
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -e .
Ready to Go: The repository includes a pre-built search index, so you can skip the index building step and run the MCP server immediately after installation!
🏃 Quick Start
1. Build the Search Index (Optional)
Note: This repository includes a pre-built search index at model/tfmod_e5_small_index.pkl with embeddings for 55 curated Terraform AWS modules. You can skip this step and proceed directly to testing or running the server if you want to use the included modules.
To rebuild the index or create a new one with additional modules:
python src/tfmod_search_cli.py index \
--docs_dir ./modules/terraform-aws-modules
Note: The first run will download the intfloat/e5-small-v2 model (~130MB).
2. Test the Search (CLI)
Test the search functionality using the command-line interface:
python src/tfmod_search_cli.py search \
--query "s3 bucket with kms encryption and versioning" \
--top_k 5
Claude Code CLI Integration
Prefer the plugin install — it configures the server and adds the workflow skills in two commands.
Option 1: Using uvx (No Clone Required)
claude mcp add terraform-modules -- uvx tfmodsearch
Or add to your Claude Code settings (~/.claude/settings.json):
{
"mcpServers": {
"terraform-modules": {
"command": "uvx",
"args": ["tfmodsearch"]
}
}
}
Note: If
uvxis not found, use the full path (runwhich uvxto find it):"command": "/Users/username/.local/bin/uvx"
Option 2: Using Local Installation
# Add the MCP server (replace with your actual path)
claude mcp add --transport stdio terraform-modules -- \
/absolute/path/to/tfmodsearch/.venv/bin/python \
/absolute/path/to/tfmodsearch/src/tfmod_mcp_server.py
# Verify the server was added
claude mcp list
Or manually add to your Claude Code settings (~/.claude/settings.json):
{
"mcpServers": {
"terraform-modules": {
"command": "/absolute/path/to/tfmodsearch/.venv/bin/python",
"args": [
"/absolute/path/to/tfmodsearch/src/tfmod_mcp_server.py"
]
}
}
}
Claude Desktop Integration
Add to your Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
Option 1: Using uvx (Recommended - No Clone Required)
{
"mcpServers": {
"terraform-modules": {
"command": "uvx",
"args": ["tfmodsearch"]
}
}
}
Note: If
uvxis not found, use the full path (runwhich uvxto find it):"command": "/Users/username/.local/bin/uvx"
Option 2: Using Local Installation
{
"mcpServers": {
"terraform-modules": {
"command": "/absolute/path/to/tfmodsearch/.venv/bin/python",
"args": [
"/absolute/path/to/tfmodsearch/src/tfmod_mcp_server.py"
]
}
}
}
Codex CLI Integration
Prefer the plugin install — it configures the server and adds the workflow skills in two commands.
For manual setup, register the server globally:
codex mcp add tfmod-search -- uvx tfmodsearch
Or add to ~/.codex/config.toml (global) or .codex/config.toml (per project):
[mcp_servers.tfmod-search]
command = "uvx"
args = ["tfmodsearch"]
startup_timeout_sec = 30 # default 10s is tight while the embedding model loads
First run: execute
uvx tfmodsearch --warmuponce beforehand — it downloads the embedding model (~220 MB) so server startup stays well within the timeout.
To nudge Codex (or any agent) to use the server proactively, add a line to your project's AGENTS.md (or CLAUDE.md for Claude Code):
Before writing Terraform that uses AWS, call the tfmod-search MCP server:
search_modules to find the module, then get_module for current variable
names and versions. Do not write module blocks from memory.
GitHub Copilot Integration (VS Code)
Add the MCP server to GitHub Copilot in VS Code (requires VS Code 1.99+):
Step 1: Create .vscode/mcp.json in your project root (or open user-level config via Command Palette: "MCP: Open User Configuration"):
Option 1: Using uvx (Recommended - No Clone Required)
{
"servers": {
"terraform-modules": {
"type": "stdio",
"command": "uvx",
"args": ["tfmodsearch"]
}
}
}
Note: If
uvxis not found, use the full path (runwhich uvxto find it).
Option 2: Using Local Installation
{
"servers": {
"terraform-modules": {
"type": "stdio",
"command": "/absolute/path/to/tfmodsearch/.venv/bin/python",
"args": [
"/absolute/path/to/tfmodsearch/src/tfmod_mcp_server.py"
]
}
}
}
Step 2: Click the "Start" button that appears at the top of the mcp.json file to initialize the server.
Step 3: Open GitHub Copilot Chat, select Agent mode from the popup menu, and click the tools icon to verify the terraform-modules server and its tools are available.
Alternative setup via Command Palette:
- Open Command Palette (
Cmd+Shift+Pon macOS /Ctrl+Shift+Pon Windows/Linux) - Run "MCP: Add Server"
- Select "stdio" as the server type
- Enter
terraform-modulesas the server name - Enter the Python path as the command
- Enter the script path as the argument
Managing MCP Servers:
- "MCP: List Servers" — view installed servers and available actions
- "MCP: Reset Cached Tools" — refresh tool discovery if tools don't appear
- "MCP: Show Output" — debug server connection issues
For more details, see VS Code MCP documentation and GitHub Copilot MCP guide.
📖 Usage
1. Building the Index
Note: The repository includes a pre-built index—you only need to build a new index if you want to add more modules or customize the existing ones.
Build or rebuild the search index from your module documentation:
python src/tfmod_search_cli.py index \
--docs_dir ./modules/terraform-aws-modules \
--index_path ./model/tfmod_e5_small_index.pkl
Options:
--docs_dir: Directory containing Terraform module markdown files (required)--index_path: Output path for the pickled index file (optional, defaults to./model/tfmod_e5_small_index.pkl)--model: Sentence transformer model to use (default:intfloat/e5-small-v2)
2. CLI Search (Standalone)
Search for modules without running the MCP server:
# Search by functionality
python src/tfmod_search_cli.py search \
--query "kubernetes cluster management" \
--top_k 3
# Search by exact module name
python src/tfmod_search_cli.py search \
--query "vpc" \
--top_k 5
# Search with custom weights
python src/tfmod_search_cli.py search \
--query "object storage" \
--w_kw 2.5 \
--w_exact 4.0 \
--w_bm25 1.5 \
--w_sem 1.0
# Search with optional query instruction for BGE models
python src/tfmod_search_cli.py search \
--query "s3" \
--query-instruction "Represent this sentence for searching relevant passages: "
🛠️ MCP Tools
The MCP server exposes four tools for Terraform module discovery and documentation retrieval:
modules_list()
List all available Terraform modules in the catalog.
Parameters: None
Returns: Complete list of modules with names, paths, descriptions, keywords, and registry coordinates (module_id, latest_version).
Example:
{
"modules": [
{
"module_name": "vpc",
"path": "modules/terraform-aws-modules/vpc.md",
"description": "Terraform module to create AWS VPC resources...",
"keywords": ["vpc", "subnet", "networking", "aws"],
"module_id": "terraform-aws-modules/vpc/aws",
"latest_version": "6.6.1"
}
],
"count": 55
}
search_modules(query: str, top_k: int = 3)
Search for Terraform modules using keywords, exact names, or natural language queries.
Parameters:
query(string): Free-text search querytop_k(int, optional): Number of results to return, 1–10 (default 3). Raise it for ambiguous queries like"iam".
Returns: Top-ranked modules with metadata and relevance scores. Each hit also carries module_id (the registry coordinate, e.g. terraform-aws-modules/vpc/aws) and latest_version, so an assistant can chain a hit straight into grep_module_docs without guessing coordinates.
Example queries:
"vpc"- Find VPC module by exact name"object storage with encryption"- Natural language search"kubernetes cluster management"- Find EKS module"serverless functions"- Find Lambda module
get_module(module_identifier: str, sections: list[str] | None = None)
Get documentation for a specific Terraform module. By default returns a compact orientation head — not the full document — so a first orientation call stays small (large modules run to 10k+ tokens in full).
Parameters:
module_identifier(string): Module name (e.g.,"vpc"), relative path (e.g.,"modules/terraform-aws-modules/vpc.md"), or submodule address (e.g.,"iam//modules/iam-role", or the full"terraform-aws-modules/iam/aws//modules/iam-role") — returns an orientation head scoped to that submodule's section in one call, instead of the whole parent doc.sections(list of strings, optional): Control what comes back.- Omitted → the orientation head: description, module info, an exact version-pin hint, notes for AI agents, any Important Gotchas the doc carries, key features, use cases, plus a footer with the full section inventory — an explicit menu of the logical keys and every heading in the doc — so the next call knows exactly what it can request. The footer also states that the curated doc is a subset and points to
grep_module_docsfor the complete, exact inputs/outputs and to module source for resource-creation conditions. For a module with submodules, the head also inlines the compact submodule inventory — each submodule's name, purpose, and pinnable source string — so when the right answer is a submodule you can pin its source or drill in via the submodule address above. - Logical keys or heading substrings → those sections added on top of the always-included core. Accepts
inputs,outputs,examples,submodules,features,use-cases,best-practices,resources, or case-insensitive substrings of headings (e.g."karpenter"for a single EKS submodule). Theinputs/outputs/exampleskeys also resolve on modules that bundle their interface into a combinedMain Module:/Root Module:section or spread it across submodules. ["all"](or"full"/"everything") → the complete document verbatim.
- Omitted → the orientation head: description, module info, an exact version-pin hint, notes for AI agents, any Important Gotchas the doc carries, key features, use cases, plus a footer with the full section inventory — an explicit menu of the logical keys and every heading in the doc — so the next call knows exactly what it can request. The footer also states that the curated doc is a subset and points to
Returns: The compact orientation head by default, a filtered subset when specific sections are requested, or the complete markdown document when an all/full key is given.
Security: Only files under the modules/ directory are accessible. Absolute paths and path traversal attempts are rejected.
grep_module_docs(module_id: str, pattern: str, version: str | None = None, ...)
Regex-grep the full, live documentation of any Terraform Registry module (not just the curated AWS catalog), optionally pinned to a specific version. The tool fetches the module's complete docs from the Terraform Registry API, assembles them into one text (README + inputs/outputs/resources rows + every submodule and example), caches them, and returns only the matching lines with surrounding context — the way a grep tool works. A single module's docs run to 100k+ tokens, so grep — not a full dump — is how you pinpoint a variable name, default, or example without flooding the context window.
Parameters:
module_id(string): Registry coordinatenamespace/name/provider(e.g."terraform-aws-modules/vpc/aws"). Comes straight from asearch_modules/modules_listresult.pattern(string): Regex (Pythonresyntax), e.g."enable_nat_gateway"or"nat_gateway|subnet".version(string, optional): Pin a version (e.g."6.6.1"). Omit forlatest.case_sensitive(bool, defaultfalse),context_lines(int, default2, 0–20),scope(list, optional — restrict toroot/inputs/outputs/resources/submodules/examples),max_matches(int, default50),refresh(bool, defaultfalse— bypass the cache).
Returns: module_id, resolved_version, source_url, total_matches/returned_matches/truncated, cache info, available_sections (all section labels in the assembled doc), and matches — each with its section label, line_number, matched line, and before/after context.
Caching: pinned versions are immutable and cached forever; latest is cached for doc_cache_ttl_hours (default 24h) and re-fetched after that or when refresh=true. Cache location: ${TFMODSEARCH_CACHE_DIR:-${XDG_CACHE_HOME:-~/.cache}}/tfmodsearch/registry_docs, overridable via config.yaml.
Example:
{
"module_id": "terraform-aws-modules/vpc/aws",
"pattern": "enable_nat_gateway",
"version": "6.6.1"
}
returns matches like:
root/readme (line 23): enable_nat_gateway = true
root/inputs (line …): - enable_nat_gateway | bool | false | Should be true if you want to provision NAT Gateways ...
Typical Workflow
A coding assistant discovers and uses a module in two steps:
-
Search by intent — the assistant turns a natural-language need into a module:
search_modules("managed kubernetes cluster with node groups") → eks (score 8.9), eks-pod-identity (2.1), autoscaling (1.7) -
Orient, then drill in — it pulls a compact orientation head, then requests the parts it needs:
get_module("iam") → IAM orientation head: what it is, exact version pin + the submodule inventory inline (iam-role, iam-policy, iam-oidc-provider, … each with a pinnable source) get_module("iam//modules/iam-role") → head scoped to the iam-role submodule (its trust policy, OIDC, inputs) in one call
The assistant then writes Terraform using real variable names and current syntax — instead of guessing. search_modules returns the top 3 candidates by default (raise top_k for broader queries) so the assistant can disambiguate between closely related modules (e.g. alb vs elb, rds vs rds-aurora) before committing. get_module returns a small orientation head by default so the first call never overflows; scoped sections=["inputs", "examples"] pull only what's needed, and sections=["all"] returns the complete document when the whole thing is genuinely wanted.
For a pinpoint lookup — the exact name/default of one variable, or how a specific feature is wired — or for a module outside the curated AWS catalog or at a specific older version, reach for grep_module_docs. search_modules/modules_list hand back a ready module_id, so the chain is direct:
grep_module_docs("terraform-aws-modules/eks/aws", "enable_cluster_creator_admin_permissions", version="20.8.5")
→ the exact input row + its default + the README lines that use it — no full-document dump
Tool boundaries: search_modules finds the right AWS module; get_module returns its curated, compact, offline doc; grep_module_docs greps the live registry docs of any module, version-pinnable, for surgical lookups.
⚙️ Configuration
Search Weights Configuration
Create a config.yaml file in the project root to customize search scoring weights:
# Optional query instruction for BGE models (improves short query retrieval)
# Set to null to disable (default), or use:
# query_instruction: "Represent this sentence for searching relevant passages: "
query_instruction: null
search_weights:
w_kw: 1.0 # Keyword overlap weight (IDF-weighted)
w_exact: 3.0 # Exact module name match boost
w_bm25: 2.0 # BM25 text relevance weight
w_sem: 3.0 # Semantic similarity weight
The values above are the weights shipped in the repo's
config.yaml(bundled with the package), so they are what the server uses out of the box.
Configuration Precedence (highest to lowest):
- CLI arguments (
--w_kw,--w_exact,--query-instruction, etc.) config.yamlfile- Built-in defaults
Module Documentation Format
Terraform module documentation files should be Markdown with YAML front-matter:
---
module_name: terraform-aws-vpc
keywords: [vpc, subnet, networking, aws]
---
# Terraform AWS VPC Module
Module description and documentation...
👩💻 Development
Setting Up Development Environment
# Clone the repository
git clone https://github.com/SantyagoSeaman/tfmodsearch.git
cd tfmodsearch
# Install with development dependencies
uv pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
Development Dependencies
The project includes the following development tools:
- pytest: Testing framework with async support
- ruff: Fast Python linter and formatter
- mypy: Static type checker
- pre-commit: Git hooks for code quality
Code Quality
# Run linter (includes flake8-bandit `S` security rules)
ruff check src/ tests/
# Run formatter
ruff format src/ tests/
# Run type checker
mypy src/
# Run all checks (linter + formatter + type checker)
pre-commit run --all-files
🧪 Testing
The project includes comprehensive integration tests covering all major functionality.
Running Tests
# Run all tests
pytest tests/ -v
# Run specific test suite
pytest tests/integration/test_all_modules_searchable.py -v # Searchability, all 55 modules (172 tests)
pytest tests/integration/test_model_comparison.py -v -s # Model comparison (31 tests)
pytest tests/integration/test_mcp_server.py -v # MCP server tools (40 tests)
pytest tests/integration/test_doc_grep.py -v # grep engine (6 tests)
pytest tests/integration/test_registry_docs.py -v # registry client + cache (6 tests)
pytest tests/integration/test_grep_module_docs.py -v # grep_module_docs tool (3 tests)
pytest tests/integration/test_parse_markdown.py -v # Markdown parsing (14 tests)
pytest tests/integration/test_cli_index.py -v # CLI index building (4 tests)
pytest tests/integration/test_security_config.py -v # Security config contract (5 tests)
# Run the opt-in live tests (real calls to the public Terraform Registry)
RUN_REGISTRY_BENCHMARK=1 pytest tests/integration/test_registry_comparison.py -v -s
RUN_REGISTRY_BENCHMARK=1 pytest tests/integration/test_grep_module_docs_live.py -v
# Run with coverage
pytest tests/ --cov=src --cov-report=term-missing --cov-report=html
Test Coverage
- All Modules Searchable (172 tests): every one of the 55 modules is verified findable by keyword, exact name, and natural-language query (target in top-3), plus catalog metadata and search-quality checks
- Model Comparison (31 tests): embedding model performance comparison with timing analysis
- MCP Server (65 tests):
search_modules,get_module, andmodules_listtools,top_kandsectionsparameters (orientation-head default, inline submodule inventory, submodule-address scoped head,all/fullescape hatch, combined/submodule interface-key resolution, version-pin hint),module_id/latest_versionfields, security validation, integration workflows - Doc Schema (386 tests): schema-integrity guards over all 55 curated docs — universal core headings present and unique (incl. the orientation head's own Key Features + Main Use Cases), a recognised interface scheme (split / combined
Main Module:/ submodule-only),inputs/outputs/examplesresolving on every doc, a clean orientation head, and every doc's submodule inventory surfaced in the head — soget_modulesection filtering can't silently break - End-to-End (59 tests): real MCP stdio protocol sessions against a spawned server process, wheel payload and entry-point verification,
uvxpackaged-server smoke test, plugin manifest/skill/agent contracts for Claude Code and Codex, skill-script tests (terraform log prefilter), live plugin install via theclaudeCLI - grep_module_docs (15 tests): the grep engine (
test_doc_grep.py, 6), the registry client + document assembly + disk cache (test_registry_docs.py, 6), and the tool wiring (test_grep_module_docs.py, 3), plus a 2-test opt-in live smoke test (test_grep_module_docs_live.py) gated byRUN_REGISTRY_BENCHMARK=1 - Module ID header (1 test): every curated doc carries a
Module IDbullet equal to its root registrySource - Markdown Parsing (14 tests):
## Module Informationparsing (name, keywords,module_id,latest_version), description extraction, normalization - CLI Index Building (4 tests): index creation, validation, search integration
- Security Config (5 tests): the Dependabot config,
SECURITY.mdreporting policy, both workflows' least-privilegepermissions, and the publish job's retained OIDCid-token: writegrant - Registry Comparison (5 tests): top-1/top-3 retrieval benchmark vs. the public Terraform Registry (see Registry Search Comparison); one network-free guard runs always, the four live tests are opt-in via
RUN_REGISTRY_BENCHMARK=1
Total: 775 tests (integration + e2e; 752 passing, 23 skip — 6 opt-in live tests unless RUN_REGISTRY_BENCHMARK=1, plus 17 docs with no submodule inventory skipped by the schema guard)
🔒 Security
TFModSearch is a local, CPU-only server that is offline by design except for one module (tfmod_registry_docs.py, which fetches public Terraform Registry documentation over HTTPS for grep_module_docs). Supply-chain and code hygiene are enforced with GitHub-native tooling:
- Vulnerability reporting: see
SECURITY.md— please use GitHub Private Vulnerability Reporting rather than a public issue. - CodeQL default setup runs static analysis on every pull request and on a weekly schedule.
- Dependabot opens weekly GitHub Actions version-updates and automatic security-update PRs for vulnerable dependencies.
- Secret scanning + push protection are enabled on the repository.
- ruff
S(flake8-bandit) security lint runs in CI, and the registry fetch refuses any non-httpsURL. - Least-privilege CI: workflows run with
permissions: contents: read; PyPI publishing uses OIDC Trusted Publishing (no long-lived token) with PEP 740 attestations.
🏗️ Architecture
Included Content
This repository includes:
-
Pre-built Search Index (
model/tfmod_e5_small_index.pkl):- Ready-to-use search index with pre-computed embeddings using
intfloat/e5-small-v2model - Contains BM25 corpus, semantic vectors, and keyword IDF scores
- Includes 55 curated Terraform AWS modules
- File size: ~4.27 MB
- Ready-to-use search index with pre-computed embeddings using
-
Curated Module Documentation (
modules/terraform-aws-modules/):- Compiled documentation for 55 Terraform AWS modules covering compute, storage, networking, databases, security, and more
- Sourced from official terraform-aws-modules project
- Formatted as Markdown with YAML front-matter metadata
- Each module includes comprehensive documentation with best practices, use cases, and examples
Indexed Modules
The search index includes 55 Terraform AWS modules across multiple service categories. Each module is documented with comprehensive descriptions, best practices, use cases, and integration examples.
Compute & Containers:
app-runner- Containerized web application deploymentsautoscaling- EC2 Auto Scaling Groupsbatch- AWS Batch for batch computing workloadsec2-instance- EC2 virtual machinesecs- Elastic Container Serviceeks- Elastic Kubernetes Serviceeks-pod-identity- EKS Pod Identity configurationlambda- Serverless functions
Networking:
alb- Application Load Balancercustomer-gateway- VPN customer gatewayelb- Classic Load Balancertransit-gateway- Transit Gateway for network hubvpc- Virtual Private Cloudvpn-gateway- VPN Gateway and Site-to-Site VPN
Storage:
ebs-optimized- EBS optimization validationecr- Elastic Container Registryefs- Elastic File Systemfsx- FSx file systems (Lustre, ONTAP, OpenZFS, Windows)s3-bucket- S3 object storage
Databases:
dms- Database Migration Servicedynamodb-table- DynamoDB NoSQL databaseelasticache- ElastiCache (Redis, Memcached)memory-db- MemoryDB for Redisopensearch- OpenSearch search and analyticsrds- Relational Database Servicerds-aurora- Aurora serverless databasesrds-proxy- RDS Proxy for connection poolingredshift- Redshift data warehouse
Security & Identity:
acm- AWS Certificate Manageriam- Identity and Access Managementkey-pair- EC2 key pairskms- Key Management Servicesecrets-manager- Secrets Managersecurity-group- VPC security groupswafv2- AWS WAF v2 web application firewall
Monitoring & Logging:
cloudwatch- CloudWatch logs and metricsdatadog-forwarders- Datadog log forwardingmanaged-service-grafana- Amazon Managed Grafanamanaged-service-prometheus- Amazon Managed Prometheus
Application Integration:
apigateway-v2- API Gateway HTTP and WebSocket APIsappsync- GraphQL API serviceeventbridge- Event-driven architecturemsk-kafka-cluster- Managed Streaming for Kafkasns- Simple Notification Servicesqs- Simple Queue Servicestep-functions- Serverless workflow orchestration
Content Delivery & Network Security:
cloudfront- CloudFront CDNglobal-accelerator- Global Accelerator for performancenetwork-firewall- AWS Network Firewallroute53- DNS and domain management
Developer Tools & Automation:
appconfig- Application configuration managementatlantis- Terraform pull request automationnotify-slack- Slack notification integrationssm-parameter- Systems Manager Parameter Store
Big Data & Analytics:
emr- Elastic MapReduce (Hadoop, Spark) for big data processing
All modules include detailed documentation with:
- Module metadata and version information
- Comprehensive feature descriptions
- Real-world use cases
- Security and operational best practices
- Integration examples and code snippets
- Links to official AWS documentation
Components
-
Search Library (
src/tfmod_search_lib.py)- Core search engine with hybrid scoring (keyword + BM25 + semantic)
- Index building and management
- Markdown parsing with YAML front-matter support
-
CLI Tool (
src/tfmod_search_cli.py)- Command-line interface for index building and testing
- Two subcommands:
indexandsearch
-
MCP Server (
src/tfmod_mcp_server.py)- FastMCP-based stdio server
- Exposes search and retrieval tools
- Configuration management and logging
Data Flow
Documentation (.md files)
↓
CLI builds index (parse + embed + BM25)
↓
Pickled index file (.pkl)
↓
MCP server loads index
↓
Tools: search_modules, get_module, modules_list
↓
Claude Desktop / MCP Clients
Search Scoring Algorithm
The hybrid search combines four signals with configurable weights:
- Keyword Overlap (
w_kw): IDF-weighted keyword matching - Exact Match (
w_exact): Boost for exact module name matches - BM25 (
w_bm25): Statistical text relevance (Okapi BM25) - Semantic Similarity (
w_sem): Cosine similarity of neural embeddings
All scores are min-max normalized before weighted combination.
Embedding Model Comparison
The project supports any sentence-transformers model via --model. The table below benchmarks 5 candidates against the full catalog: all 54 modules, each queried 3 ways (exact name, keyword, natural language) — 162 queries per model — using the production search weights from config.yaml.
| Model | Dim | Size | Build | Avg Query | Success Rate (top-3) |
|---|---|---|---|---|---|
intfloat/e5-small-v2 ⭐ (default, since 0.6.0) |
384 | ~138 MB | ~4.1s | ~8 ms | 100.0% |
BAAI/bge-base-en-v1.5 (default through 0.5.0) |
768 | ~419 MB | ~7.5s | ~23 ms | 100.0% |
thenlper/gte-small |
384 | ~65 MB | ~3.9s | ~8 ms | 98.8% |
BAAI/bge-small-en-v1.5 |
384 | ~129 MB | ~4.1s | ~8 ms | 98.8% |
sentence-transformers/all-MiniLM-L12-v2 |
384 | ~129 MB | ~3.0s | ~8 ms | 98.1% |
Why e5-small-v2: it's the only smaller model that matches bge-base-en-v1.5's 100% success rate on this corpus — at ~3x smaller and ~3x faster per query, with no weight retuning or query/passage prompt prefixes required (both were tried; neither moved the needle on this corpus). gte-small is the smallest option (~65 MB, ~6x smaller than the old default) and is a reasonable choice if binary size matters more than the last percentage point of recall — see tests/integration/test_model_comparison.py for a repo-committed, CI-run comparison across all three (gte-small, bge-base-en-v1.5, e5-small-v2). bge-small-en-v1.5 and all-MiniLM-L12-v2 were also evaluated and are documented here for completeness, but weren't selected: no clear edge over gte-small/e5-small-v2 on either size or accuracy.
To use a different model, rebuild the index with --model <name> (see Building the Index) and point --index_path / config.yaml at the new file.
Registry Search Comparison (vs. Terraform Registry / HashiCorp MCP)
How does hybrid-semantic search actually compare to a plain keyword lookup against the public Terraform Registry? The registry endpoint used below — GET /v1/modules/search?q=…&provider=aws — is exactly the API that the official hashicorp/terraform-mcp-server's search_modules tool wraps, so this doubles as an apples-to-apples comparison against that competitor's module search.
The benchmark reuses the same golden set as the searchability tests: all 54 modules, each queried 3 ways (exact name, keyword, natural language) — 162 labeled queries — asking "is the expected module in the top-1 / top-3 results?". Registry results are scored two ways: official (hit only if the match comes from the terraform-aws-modules namespace — the same module we document) and any-author (hit if any namespace returns a name-matching module — deliberately generous to the registry).
| Query type | System | Top-1 | Top-3 |
|---|---|---|---|
| keyword (n=54) | TFModSearch (semantic, AWS curated) | 94.4% | 100.0% |
Registry — official terraform-aws-modules |
22.2% | 22.2% | |
| Registry — any-author aws module | 29.6% | 38.9% | |
| exact-name (n=54) | TFModSearch | 100.0% | 100.0% |
| Registry — official | 100.0% | 100.0% | |
| Registry — any-author | 100.0% | 100.0% | |
| natural-lang (n=54) | TFModSearch | 81.5% | 100.0% |
| Registry — official | 5.6% | 5.6% | |
| Registry — any-author | 13.0% | 14.8% | |
| OVERALL (n=162) | TFModSearch | 92.0% | 100.0% |
| Registry — official | 42.6% | 42.6% | |
| Registry — any-author | 47.5% | 51.2% |
Takeaways:
- This is a retrieval-quality story, not a coverage story. All 54/54 curated modules exist as standalone official
terraform-aws-modulesentries in the registry — the registry can return every one; it just doesn't surface them from a descriptive query. - Semantic search dominates on natural language. For free-text queries (e.g.
managed kubernetes cluster→eks,serverless function execution→lambda), TFModSearch keeps 100% top-3 while the official registry search lands 5.6% (misses 51 of 54). Keyword search only matches when the query already contains the module's terms. - The registry's failure mode is "not in results at all," not mis-ranking. Its official top-1 equals top-3 in every row — when the right module appears it's already #1 (download counts float it up); the problem is that for descriptive queries it doesn't appear.
- Parity only on exact names. If you already know the module name (
vpc,s3-bucket), keyword search is fine — the entire value of semantic search is in the other two rows, where a user describes a task rather than a name. - Caveats (in the registry's favor): the natural-language queries were authored alongside this corpus (mild home-turf bias, though they are generic task descriptions), and
any-authorcredits the registry for same-named modules from any author — yet it still trails at 51.2% top-3 vs. our 100%.
Reproduce it yourself (opt-in, makes live calls to the public registry):
RUN_REGISTRY_BENCHMARK=1 pytest tests/integration/test_registry_comparison.py -v -s
The comparison is committed as tests/integration/test_registry_comparison.py. It stays hermetic in normal CI (live tests skip unless RUN_REGISTRY_BENCHMARK=1, and skip gracefully if the registry is unreachable); a network-free guard test pins the "100% top-3" figure on our side.
🤝 Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and code quality checks
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow PEP 8 style guidelines (enforced by
ruff) - Add type hints to all functions (checked by
mypy) - Write tests for new features
- Update documentation as needed
- Keep commits atomic and well-described
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with FastMCP - FastAPI-style MCP server framework
- Uses sentence-transformers for semantic embeddings
- Terraform module documentation from terraform-aws-modules
📞 Support
For questions, issues, or feature requests:
- Open an issue on GitHub Issues
- Check existing issues for common problems and solutions
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tfmodsearch-0.15.0.tar.gz.
File metadata
- Download URL: tfmodsearch-0.15.0.tar.gz
- Upload date:
- Size: 1.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48cbf7bf1bea8102fa75c052c6a48c5544c55ec2f8a04be210797f7b7de74276
|
|
| MD5 |
6fd6c07b4a0d6a80e9feadb6d225cd2f
|
|
| BLAKE2b-256 |
0a32517b6e9994968c8d95105c76b1f6437bedcd6cee3d56dc19913e9b1698a9
|
Provenance
The following attestation bundles were made for tfmodsearch-0.15.0.tar.gz:
Publisher:
publish.yml on SantyagoSeaman/tfmodsearch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tfmodsearch-0.15.0.tar.gz -
Subject digest:
48cbf7bf1bea8102fa75c052c6a48c5544c55ec2f8a04be210797f7b7de74276 - Sigstore transparency entry: 2165139391
- Sigstore integration time:
-
Permalink:
SantyagoSeaman/tfmodsearch@d56536ee61f9809549dbf68bd4095ff61af49ddc -
Branch / Tag:
refs/tags/v0.15.0 - Owner: https://github.com/SantyagoSeaman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d56536ee61f9809549dbf68bd4095ff61af49ddc -
Trigger Event:
push
-
Statement type:
File details
Details for the file tfmodsearch-0.15.0-py3-none-any.whl.
File metadata
- Download URL: tfmodsearch-0.15.0-py3-none-any.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a27bdeb78a0db4561c4a7c0fdf980cf6e1dd85e83131107cff86a6093b4195ca
|
|
| MD5 |
58770bd27acedd272f46967a5b5f9f22
|
|
| BLAKE2b-256 |
4f2086348b2ba9ca48bec74ec0946d5e77de6255f59b7b194a7109d1a1cc48f3
|
Provenance
The following attestation bundles were made for tfmodsearch-0.15.0-py3-none-any.whl:
Publisher:
publish.yml on SantyagoSeaman/tfmodsearch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tfmodsearch-0.15.0-py3-none-any.whl -
Subject digest:
a27bdeb78a0db4561c4a7c0fdf980cf6e1dd85e83131107cff86a6093b4195ca - Sigstore transparency entry: 2165139408
- Sigstore integration time:
-
Permalink:
SantyagoSeaman/tfmodsearch@d56536ee61f9809549dbf68bd4095ff61af49ddc -
Branch / Tag:
refs/tags/v0.15.0 - Owner: https://github.com/SantyagoSeaman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d56536ee61f9809549dbf68bd4095ff61af49ddc -
Trigger Event:
push
-
Statement type: