terraform-cloud-mcp
by severity1
Verified
- api
"""Terraform Cloud API client"""
import os
import logging
from typing import Optional, Dict, Tuple
import httpx
# Constants
TERRAFORM_CLOUD_API_URL = "https://app.terraform.io/api/v2"
# Store default token from environment variable
# Security note: API tokens are sensitive data and should never be logged or exposed
DEFAULT_TOKEN = os.getenv("TFC_TOKEN")
if DEFAULT_TOKEN:
logging.info("Default token provided (masked for security)")
async def make_api_request(path: str, method: str = "GET", token: Optional[str] = None, params: dict = {}, data: dict = {}) -> Tuple[bool, dict]:
"""
Make a request to the Terraform Cloud API
Args:
path: API path to request (without base URL)
method: HTTP method (default: GET)
token: API token (defaults to DEFAULT_TOKEN)
params: Query parameters for the request (optional)
data: JSON data for POST/PATCH requests (optional)
Returns:
Tuple of (success, data) where data is either the response JSON or an error dict
"""
if not token:
token = DEFAULT_TOKEN
if not token:
return (False, {"error": "Token is required. Please set the TFC_TOKEN environment variable."})
try:
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/vnd.api+json"}
async with httpx.AsyncClient() as client:
url = f"{TERRAFORM_CLOUD_API_URL}/{path}"
if method == "GET":
response = await client.get(url, headers=headers, params=params)
elif method == "POST":
response = await client.post(url, headers=headers, params=params, json=data)
elif method == "PATCH":
response = await client.patch(url, headers=headers, params=params, json=data)
elif method == "DELETE":
response = await client.delete(url, headers=headers, params=params)
else:
return (False, {"error": f"Unsupported method: {method}"})
# Handle different success response codes
if response.status_code in [200, 201, 202, 204]:
if response.status_code == 204: # No content
return (True, {"status": "success"})
try:
return (True, response.json())
except ValueError:
return (True, {"status": "success", "raw_response": response.text})
else:
error_message = f"API request failed: {response.status_code}"
try:
error_data = response.json()
return (False, {"error": error_message, "details": error_data})
except ValueError:
return (False, {"error": error_message})
except Exception as e:
# Ensure no sensitive data is included in error messages
error_message = str(e)
if token and token in error_message:
error_message = error_message.replace(token, "[REDACTED]")
return (False, {"error": f"Request error: {error_message}"})