Skip to main content
Glama

Terraform Registry MCP Server

by thrashr888
resourceUsage.ts20.3 kB
import { ResourceUsageInput, ResponseContent } from "../types/index.js"; import { createStandardResponse, addStandardContext } from "../utils/responseUtils.js"; import { handleToolError } from "../utils/responseUtils.js"; import { REGISTRY_API_BASE } from "../../config.js"; import logger from "../utils/logger.js"; interface Example { name: string; description: string; code: string; tags: string[]; dependencies?: string[]; variables?: { name: string; type: string; description: string; }[]; outputs?: { name: string; description: string; }[]; } interface CommonPattern { pattern: string; description: string; bestPractices: string[]; caveats: string[]; code: string; } interface Prerequisites { requiredProviders: string[]; requiredResources: string[]; permissions: string[]; limitations: string[]; } interface TroubleshootingItem { problem: string; cause: string; solution: string; code?: string; } /** * Extract example blocks from markdown content with enhanced metadata */ function extractExamples(content: string): Example[] { const examples: Example[] = []; const exampleMatches = content.matchAll(/### ([^\n]+)\n\n(.*?)```(?:hcl|terraform)?\n([\s\S]*?)```/g); for (const match of exampleMatches) { const name = match[1].trim(); const description = match[2].trim(); const code = match[3].trim(); // Extract tags from description or name const tags = extractTags(description); // Extract dependencies from code const dependencies = extractDependencies(code); // Extract variables and outputs const variables = extractVariables(code); const outputs = extractOutputs(code); examples.push({ name, description, code, tags, dependencies, variables, outputs }); } return examples; } /** * Extract tags from text based on common patterns and keywords */ function extractTags(text: string): string[] { const tags: Set<string> = new Set(); // Common configuration patterns if (text.toLowerCase().includes("encryption")) tags.add("encryption"); if (text.toLowerCase().includes("backup")) tags.add("backup"); if (text.toLowerCase().includes("global")) tags.add("global-tables"); if (text.toLowerCase().includes("autoscal")) tags.add("autoscaling"); if (text.toLowerCase().includes("basic")) tags.add("basic"); if (text.toLowerCase().includes("advanced")) tags.add("advanced"); return Array.from(tags); } /** * Extract dependencies from HCL code */ function extractDependencies(code: string): string[] { const dependencies: Set<string> = new Set(); const resourceMatches = code.matchAll(/resource\s+"([^"]+)"/g); for (const match of resourceMatches) { dependencies.add(match[1]); } return Array.from(dependencies); } /** * Extract variables from HCL code */ function extractVariables(code: string): { name: string; type: string; description: string }[] { const variables: { name: string; type: string; description: string }[] = []; const varMatches = code.matchAll(/variable\s+"([^"]+)"\s*{([^}]+)}/g); for (const match of varMatches) { const name = match[1]; const block = match[2]; const type = block.match(/type\s*=\s*([^\n]+)/)?.[1] || "string"; const description = block.match(/description\s*=\s*"([^"]+)"/)?.[1] || ""; variables.push({ name, type, description }); } return variables; } /** * Extract outputs from HCL code */ function extractOutputs(code: string): { name: string; description: string }[] { const outputs: { name: string; description: string }[] = []; const outputMatches = code.matchAll(/output\s+"([^"]+)"\s*{([^}]+)}/g); for (const match of outputMatches) { const name = match[1]; const block = match[2]; const description = block.match(/description\s*=\s*"([^"]+)"/)?.[1] || ""; outputs.push({ name, description }); } return outputs; } /** * Extract common patterns from markdown content */ function extractCommonPatterns(content: string): CommonPattern[] { const patterns: CommonPattern[] = []; // Look for sections that describe common patterns const patternMatches = content.matchAll(/## ([^\n]+Pattern[^\n]*)\n\n(.*?)(?=\n##|$)/gs); for (const match of patternMatches) { const pattern = match[1].trim(); const section = match[2]; // Extract description const description = section.match(/(?:^|\n\n)([^#\n].+?)(?=\n\n|$)/)?.[1] || ""; // Extract best practices const bestPractices = extractListItems(section, "Best Practices"); // Extract caveats const caveats = extractListItems(section, "Caveats"); // Extract code example const code = section.match(/```(?:hcl|terraform)?\n([\s\S]*?)```/)?.[1] || ""; patterns.push({ pattern, description, bestPractices, caveats, code }); } return patterns; } /** * Extract prerequisites from markdown content */ function extractPrerequisites(content: string): Prerequisites { const prerequisites: Prerequisites = { requiredProviders: [], requiredResources: [], permissions: [], limitations: [] }; // Extract required providers const providerMatches = content.matchAll(/required_providers\s*{([^}]+)}/g); for (const match of providerMatches) { const providers = match[1].match(/[a-zA-Z0-9_-]+\s*=/g) || []; prerequisites.requiredProviders = providers.map((p) => p.replace(/\s*=$/, "")); } // Extract required resources from examples const resourceMatches = content.matchAll(/resource\s+"([^"]+)"/g); for (const match of resourceMatches) { prerequisites.requiredResources.push(match[1]); } // Extract permissions from notes or requirements sections const permissionSection = content.match(/(?:##\s*Permissions|##\s*IAM[^\n]*)\n\n(.*?)(?=\n##|$)/s); if (permissionSection) { prerequisites.permissions = extractListItems(permissionSection[1]); } // Extract limitations from notes or limitations sections const limitationSection = content.match(/(?:##\s*Limitations|##\s*Restrictions)[^\n]*\n\n(.*?)(?=\n##|$)/s); if (limitationSection) { prerequisites.limitations = extractListItems(limitationSection[1]); } return prerequisites; } /** * Extract troubleshooting items from markdown content */ function extractTroubleshooting(content: string): TroubleshootingItem[] { const items: TroubleshootingItem[] = []; // Look for troubleshooting or common issues sections const troubleshootingSection = content.match(/##\s*(?:Troubleshooting|Common Issues)[^\n]*\n\n(.*?)(?=\n##|$)/s); if (!troubleshootingSection) return items; const section = troubleshootingSection[1]; const problemMatches = section.matchAll(/###\s*([^\n]+)\n\n(.*?)(?=\n###|$)/gs); for (const match of problemMatches) { const problem = match[1].trim(); const details = match[2]; // Extract cause and solution const cause = details.match(/(?:Cause|Why):\s*([^\n]+)/)?.[1] || ""; const solution = details.match(/(?:Solution|Fix|Resolution):\s*([^\n]+)/)?.[1] || ""; // Extract code example if present const code = details.match(/```(?:hcl|terraform)?\n([\s\S]*?)```/)?.[1]; items.push({ problem, cause, solution, code }); } return items; } /** * Helper function to extract list items from a section */ function extractListItems(content: string, sectionTitle?: string): string[] { if (sectionTitle) { const section = content.match(new RegExp(`${sectionTitle}[^\n]*\n\n(.*?)(?=\n##|$)`, "s")); if (!section) return []; content = section[1]; } const items = content.match(/[-*]\s+([^\n]+)/g) || []; return items.map((item) => item.replace(/^[-*]\s+/, "").trim()); } /** * Extract notes from markdown content */ function extractNotes(content: string): string[] { const notes: string[] = []; const noteMatches = content.matchAll(/>\s*\*\*Note:\*\*\s*(.*?)(?=\n\n|\n>|\n#|$)/gs); for (const match of noteMatches) { notes.push(match[1].trim()); } return notes; } /** * Extract import instructions from markdown content */ function extractImportInstructions(content: string): string { const importSection = content.match(/## Import\n\n([\s\S]*?)(?=\n##|$)/)?.[1] || ""; return importSection.trim(); } /** * Extract related documentation links from markdown content */ function extractRelatedDocs(content: string): Array<{ title: string; url: string }> { const relatedDocs: Array<{ title: string; url: string }> = []; const linkMatches = content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g); for (const match of linkMatches) { const title = match[1]; const url = match[2]; // Only include external links and exclude section links if (url.startsWith("http") && !url.includes("#")) { relatedDocs.push({ title, url }); } } return relatedDocs; } /** * Extract subcategory from markdown content */ function extractSubcategory(content: string): string { return content.match(/subcategory: "([^"]+)"/)?.[1] || ""; } /** * Extract description from markdown content */ function extractDescription(content: string): string { return content.match(/description: \|-\n(.*?)\n---/s)?.[1]?.trim() || ""; } /** * Handles the resourceUsage tool request * @param params Input parameters for resource usage * @returns Standardized response with resource usage example */ export async function handleResourceUsage(params: ResourceUsageInput): Promise<ResponseContent> { try { logger.debug("Processing resourceUsage request", params); // Extract resource parameters, handling either { provider, resource } or { name } format const resourceParams: ResourceUsageInput = { provider: params.provider, resource: params.resource || params.name }; // Validate required parameters if (!resourceParams.provider) { throw new Error("Provider parameter is required for resourceUsage"); } if (!resourceParams.resource) { throw new Error("Resource parameter is required for resourceUsage"); } // 1. Get provider versions const versionsUrl = `${REGISTRY_API_BASE}/v2/providers/hashicorp/${resourceParams.provider}?include=provider-versions`; logger.info("Fetching versions from:", versionsUrl); const versionsResponse = await fetch(versionsUrl); if (!versionsResponse.ok) { throw new Error(`Failed to fetch provider versions: ${versionsResponse.status} ${versionsResponse.statusText}`); } const versionsData = await versionsResponse.json(); logger.debug("Versions response:", versionsData); if (!versionsData.included || versionsData.included.length === 0) { throw new Error(`No versions found for provider hashicorp/${resourceParams.provider}`); } // Get latest version ID and published date const versionId = versionsData.included[0].id; const publishedAt = versionsData.included[0].attributes["published-at"]; const version = versionsData.included[0].attributes.version; logger.info("Using version:", { versionId, version, publishedAt }); // 2. Get resource documentation ID // Updated URL construction to use the correct format const docIdUrl = `${REGISTRY_API_BASE}/v2/provider-docs?filter[provider-version]=${versionId}&filter[category]=resources&filter[slug]=${resourceParams.resource}&filter[language]=hcl&page[size]=1`; logger.info("Fetching doc ID from:", docIdUrl); const docIdResponse = await fetch(docIdUrl); if (!docIdResponse.ok) { logger.error("Failed to fetch documentation:", { status: docIdResponse.status, statusText: docIdResponse.statusText, url: docIdUrl }); throw new Error(`Failed to fetch documentation ID: ${docIdResponse.status} ${docIdResponse.statusText}`); } const docIdData = await docIdResponse.json(); logger.debug("Documentation response:", docIdData); if (!docIdData.data || docIdData.data.length === 0) { throw new Error(`Documentation not found for resource ${resourceParams.resource}`); } const docId = docIdData.data[0].id; logger.info("Found documentation ID:", docId); // 3. Get full documentation content const contentUrl = `${REGISTRY_API_BASE}/v2/provider-docs/${docId}`; logger.info("Fetching content from:", contentUrl); const contentResponse = await fetch(contentUrl); if (!contentResponse.ok) { logger.error("Failed to fetch content:", { status: contentResponse.status, statusText: contentResponse.statusText, url: contentUrl }); throw new Error(`Failed to fetch documentation content: ${contentResponse.status} ${contentResponse.statusText}`); } const contentData = await contentResponse.json(); logger.debug("Content response:", contentData); if (!contentData.data?.attributes?.content) { throw new Error("Documentation content is empty or malformed"); } const content = contentData.data.attributes.content; const documentationUrl = `${REGISTRY_API_BASE}/providers/hashicorp/${resourceParams.provider}/${version}/docs/resources/${resourceParams.resource}`; // Extract all documentation components with enhanced metadata const description = extractDescription(content); const subcategory = extractSubcategory(content); const notes = extractNotes(content); const examples = extractExamples(content); const patterns = extractCommonPatterns(content); const prerequisites = extractPrerequisites(content); const troubleshooting = extractTroubleshooting(content); const importInstructions = extractImportInstructions(content); const relatedDocs = extractRelatedDocs(content); // Format the response in markdown with enhanced sections let markdownResponse = `## ${resourceParams.resource}\n\n`; markdownResponse += `This resource is provided by the **hashicorp/${resourceParams.provider}** provider version ${version}.\n\n`; if (description) { markdownResponse += `${description}\n\n`; } if (subcategory) { markdownResponse += `**Category**: ${subcategory}\n\n`; } // Add prerequisites section if (prerequisites.requiredProviders.length > 0 || prerequisites.requiredResources.length > 0) { markdownResponse += "## Prerequisites\n\n"; if (prerequisites.requiredProviders.length > 0) { markdownResponse += "### Required Providers\n\n"; prerequisites.requiredProviders.forEach((provider) => { markdownResponse += `* ${provider}\n`; }); markdownResponse += "\n"; } if (prerequisites.requiredResources.length > 0) { markdownResponse += "### Required Resources\n\n"; prerequisites.requiredResources.forEach((resource) => { markdownResponse += `* ${resource}\n`; }); markdownResponse += "\n"; } if (prerequisites.permissions.length > 0) { markdownResponse += "### Required Permissions\n\n"; prerequisites.permissions.forEach((permission) => { markdownResponse += `* ${permission}\n`; }); markdownResponse += "\n"; } if (prerequisites.limitations.length > 0) { markdownResponse += "### Limitations\n\n"; prerequisites.limitations.forEach((limitation) => { markdownResponse += `* ${limitation}\n`; }); markdownResponse += "\n"; } } if (notes.length > 0) { markdownResponse += "## Important Notes\n\n"; for (const note of notes) { markdownResponse += `> **Note**: ${note}\n\n`; } } if (examples.length > 0) { markdownResponse += "## Examples\n\n"; for (const example of examples) { markdownResponse += `### ${example.name}\n\n`; if (example.tags.length > 0) { markdownResponse += `**Tags**: ${example.tags.join(", ")}\n\n`; } if (example.description) { markdownResponse += `${example.description}\n\n`; } if (example.dependencies?.length) { markdownResponse += "**Dependencies**:\n"; example.dependencies.forEach((dep) => { markdownResponse += `* ${dep}\n`; }); markdownResponse += "\n"; } if (example.variables?.length) { markdownResponse += "**Variables**:\n"; example.variables.forEach((v) => { markdownResponse += `* \`${v.name}\` (${v.type}): ${v.description}\n`; }); markdownResponse += "\n"; } markdownResponse += "```hcl\n" + example.code + "\n```\n\n"; if (example.outputs?.length) { markdownResponse += "**Outputs**:\n"; example.outputs.forEach((o) => { markdownResponse += `* \`${o.name}\`: ${o.description}\n`; }); markdownResponse += "\n"; } } } if (patterns.length > 0) { markdownResponse += "## Common Patterns\n\n"; for (const pattern of patterns) { markdownResponse += `### ${pattern.pattern}\n\n`; markdownResponse += `${pattern.description}\n\n`; if (pattern.bestPractices.length > 0) { markdownResponse += "**Best Practices**:\n"; pattern.bestPractices.forEach((practice) => { markdownResponse += `* ${practice}\n`; }); markdownResponse += "\n"; } if (pattern.caveats.length > 0) { markdownResponse += "**Caveats**:\n"; pattern.caveats.forEach((caveat) => { markdownResponse += `* ${caveat}\n`; }); markdownResponse += "\n"; } if (pattern.code) { markdownResponse += "```hcl\n" + pattern.code + "\n```\n\n"; } } } if (troubleshooting.length > 0) { markdownResponse += "## Troubleshooting\n\n"; for (const item of troubleshooting) { markdownResponse += `### ${item.problem}\n\n`; markdownResponse += `**Cause**: ${item.cause}\n\n`; markdownResponse += `**Solution**: ${item.solution}\n\n`; if (item.code) { markdownResponse += "```hcl\n" + item.code + "\n```\n\n"; } } } if (importInstructions) { markdownResponse += "## Import\n\n"; markdownResponse += `${importInstructions}\n\n`; } if (relatedDocs.length > 0) { markdownResponse += "## Related Documentation\n\n"; for (const doc of relatedDocs) { markdownResponse += `* [${doc.title}](${doc.url})\n`; } markdownResponse += "\n"; } // Add documentation link markdownResponse += `\n**[View Full Documentation](${documentationUrl})**\n`; // Prepare metadata with enhanced information const metadata = { provider: resourceParams.provider, namespace: "hashicorp", resource: resourceParams.resource, version: { current: version, publishedAt }, documentationUrl, description, subcategory, examples: examples.map((e) => ({ name: e.name, tags: e.tags, dependencies: e.dependencies, variables: e.variables?.map((v) => v.name), outputs: e.outputs?.map((o) => o.name) })), patterns: patterns.map((p) => ({ pattern: p.pattern, bestPractices: p.bestPractices.length, caveats: p.caveats.length })), prerequisites: { providers: prerequisites.requiredProviders, resources: prerequisites.requiredResources, hasPermissions: prerequisites.permissions.length > 0, hasLimitations: prerequisites.limitations.length > 0 }, troubleshooting: troubleshooting.map((t) => ({ problem: t.problem, hasSolution: !!t.solution })), notes, import_instructions: importInstructions, related_docs: relatedDocs, context: { compatibility: { terraformCoreVersions: "Terraform 0.12 and later", lastUpdated: publishedAt } } }; // Add standard context addStandardContext(metadata); return createStandardResponse("success", markdownResponse, metadata); } catch (error) { return handleToolError("resourceUsage", error, { inputParams: params }); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/thrashr888/terraform-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server