Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

IMPROVEMENTS

* Add `limit`, `provider`, `namespace`, and `verified` parameters to `search_modules` tool for pagination and filtering to reduce API costs [271](https://github.com/hashicorp/terraform-mcp-server/pull/271)
* Set custom User-Agent header for TFE API requests to enable tracking MCP server usage separately from other go-tfe clients [268](https://github.com/hashicorp/terraform-mcp-server/pull/268)

## 0.4.0
Expand Down
39 changes: 35 additions & 4 deletions pkg/tools/registry/search_modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ If no modules were found, reattempt the search with a new moduleName query.`),
mcp.Min(0),
mcp.DefaultNumber(0),
),
mcp.WithNumber("limit",
mcp.Description("Maximum number of results to return (default: 10, max: 100)"),
mcp.Min(1),
mcp.DefaultNumber(10),
),
mcp.WithString("provider",
mcp.Description("Filter results to a specific provider (e.g., 'aws', 'google', 'azurerm')"),
),
mcp.WithString("namespace",
mcp.Description("Filter results to a specific namespace"),
),
mcp.WithBoolean("verified",
mcp.Description("If true, only return verified partner modules"),
),
),
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return getSearchModulesHandler(ctx, request, logger)
Expand All @@ -58,13 +72,20 @@ func getSearchModulesHandler(ctx context.Context, request mcp.CallToolRequest, l
}
moduleQuery = strings.ToLower(moduleQuery)
currentOffsetValue := request.GetInt("current_offset", 0)
limitValue := request.GetInt("limit", 10)
if limitValue > 100 {
limitValue = 100 // Cap at 100 to prevent excessive API usage
}
providerFilter := request.GetString("provider", "")
namespaceFilter := request.GetString("namespace", "")
verifiedOnly := request.GetBool("verified", false)

httpClient, err := client.GetHttpClientFromContext(ctx, logger)
if err != nil {
return ToolError(logger, "failed to get http client for public Terraform registry", err)
}

response, err := sendSearchModulesCall(httpClient, moduleQuery, currentOffsetValue, logger)
response, err := sendSearchModulesCall(httpClient, moduleQuery, currentOffsetValue, limitValue, providerFilter, namespaceFilter, verifiedOnly, logger)
if err != nil {
return ToolErrorf(logger, "no modules found for query: %s - try a different search term", moduleQuery)
}
Expand All @@ -81,12 +102,22 @@ func getSearchModulesHandler(ctx context.Context, request mcp.CallToolRequest, l
return mcp.NewToolResultText(modulesData), nil
}

func sendSearchModulesCall(providerClient *http.Client, moduleQuery string, currentOffset int, logger *log.Logger) ([]byte, error) {
func sendSearchModulesCall(providerClient *http.Client, moduleQuery string, currentOffset, limit int, provider, namespace string, verified bool, logger *log.Logger) ([]byte, error) {
uri := "modules"
if moduleQuery != "" {
uri = fmt.Sprintf("%s/search?q='%s'&offset=%v", uri, url.PathEscape(moduleQuery), currentOffset)
uri = fmt.Sprintf("%s/search?q='%s'&offset=%v&limit=%v", uri, url.PathEscape(moduleQuery), currentOffset, limit)
} else {
uri = fmt.Sprintf("%s?offset=%v", uri, currentOffset)
uri = fmt.Sprintf("%s?offset=%v&limit=%v", uri, currentOffset, limit)
}
// Add optional filters
if provider != "" {
uri = fmt.Sprintf("%s&provider=%s", uri, url.PathEscape(provider))
}
if namespace != "" {
uri = fmt.Sprintf("%s&namespace=%s", uri, url.PathEscape(namespace))
}
if verified {
uri = fmt.Sprintf("%s&verified=true", uri)
}

response, err := client.SendRegistryCall(providerClient, "GET", uri, logger)
Expand Down