Skip to main content
Glama

Terraform Cloud MCP Server

by matchs
index.ts9 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { homedir } from 'os'; import { readFileSync } from 'fs'; import { join } from 'path'; // Terraform Cloud API configuration const TF_API_BASE = 'https://app.terraform.io/api/v2'; // Read Terraform Cloud token from credentials file function getTerraformToken(): string { const credentialsPath = join(homedir(), '.terraform.d', 'credentials.tfrc.json'); try { const credentials = JSON.parse(readFileSync(credentialsPath, 'utf-8')); return credentials.credentials['app.terraform.io'].token; } catch (error) { throw new Error('Failed to read Terraform Cloud token from ~/.terraform.d/credentials.tfrc.json'); } } // Make authenticated requests to Terraform Cloud API async function tfCloudRequest(endpoint: string): Promise<any> { const token = getTerraformToken(); const response = await fetch(`${TF_API_BASE}${endpoint}`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/vnd.api+json' } }); if (!response.ok) { throw new Error(`Terraform Cloud API error: ${response.statusText}`); } return response.json(); } // Create MCP server const server = new McpServer({ name: 'terraform-cloud-server', version: '1.0.0' }); // Tool: Get workspace run status server.registerTool( 'get_run_status', { title: 'Get Run Status', description: 'Get the current run status for a Terraform Cloud workspace', inputSchema: { workspaceName: z.string().describe('Workspace name'), organization: z.string().default('urbanmedia').describe('Organization name') }, outputSchema: { workspaceId: z.string(), workspaceName: z.string(), currentRunId: z.string().optional(), currentRunStatus: z.string().optional(), recentRuns: z.array(z.object({ id: z.string(), status: z.string(), createdAt: z.string(), message: z.string() })) } }, async ({ workspaceName, organization }) => { try { // Get workspace details const workspaceData = await tfCloudRequest(`/organizations/${organization}/workspaces/${workspaceName}`); const workspaceId = workspaceData.data.id; // Get current run if exists const currentRunData = workspaceData.data.relationships['current-run']?.data; let currentRunId: string | undefined; let currentRunStatus: string | undefined; if (currentRunData?.id) { currentRunId = currentRunData.id; const runData = await tfCloudRequest(`/runs/${currentRunId}`); currentRunStatus = runData.data.attributes.status; } // Get recent runs const runsData = await tfCloudRequest(`/workspaces/${workspaceId}/runs?page[size]=5`); const recentRuns = runsData.data.map((run: any) => ({ id: run.id, status: run.attributes.status, createdAt: run.attributes['created-at'], message: run.attributes.message || 'No message' })); const output = { workspaceId, workspaceName, currentRunId, currentRunStatus, recentRuns }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true }; } } ); // Tool: List workspaces server.registerTool( 'list_workspaces', { title: 'List Workspaces', description: 'List all workspaces in a Terraform Cloud organization', inputSchema: { organization: z.string().default('urbanmedia').describe('Organization name') }, outputSchema: { workspaces: z.array(z.object({ id: z.string(), name: z.string(), locked: z.boolean(), executionMode: z.string(), currentRunStatus: z.string().optional() })) } }, async ({ organization }) => { try { const data = await tfCloudRequest(`/organizations/${organization}/workspaces`); const workspaces = data.data.map((ws: any) => ({ id: ws.id, name: ws.attributes.name, locked: ws.attributes.locked, executionMode: ws.attributes['execution-mode'], currentRunStatus: ws.relationships['current-run']?.data ? 'active' : 'idle' })); const output = { workspaces }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true }; } } ); // Tool: Get workspace details server.registerTool( 'get_workspace_details', { title: 'Get Workspace Details', description: 'Get detailed information about a Terraform Cloud workspace', inputSchema: { workspaceName: z.string().describe('Workspace name'), organization: z.string().default('urbanmedia').describe('Organization name') }, outputSchema: { id: z.string(), name: z.string(), locked: z.boolean(), executionMode: z.string(), autoApply: z.boolean(), terraformVersion: z.string(), workingDirectory: z.string().optional(), vcsRepo: z.object({ identifier: z.string(), branch: z.string() }).optional() } }, async ({ workspaceName, organization }) => { try { const data = await tfCloudRequest(`/organizations/${organization}/workspaces/${workspaceName}`); const attrs = data.data.attributes; const output = { id: data.data.id, name: attrs.name, locked: attrs.locked, executionMode: attrs['execution-mode'], autoApply: attrs['auto-apply'], terraformVersion: attrs['terraform-version'], workingDirectory: attrs['working-directory'] || undefined, vcsRepo: attrs['vcs-repo'] ? { identifier: attrs['vcs-repo'].identifier, branch: attrs['vcs-repo'].branch } : undefined }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true }; } } ); // Tool: Get run details by ID server.registerTool( 'get_run_details', { title: 'Get Run Details', description: 'Get detailed information about a specific Terraform Cloud run by its ID', inputSchema: { runId: z.string().describe('Run ID (e.g., run-abc123)') }, outputSchema: { id: z.string(), status: z.string(), message: z.string(), createdAt: z.string(), source: z.string(), isConfirmable: z.boolean(), hasChanges: z.boolean(), planStatus: z.string().optional(), applyStatus: z.string().optional(), workspace: z.object({ id: z.string(), name: z.string() }), configurationVersion: z.object({ id: z.string(), source: z.string() }).optional() } }, async ({ runId }) => { try { const data = await tfCloudRequest(`/runs/${runId}`); const attrs = data.data.attributes; const relationships = data.data.relationships; const output = { id: data.data.id, status: attrs.status, message: attrs.message || 'No message', createdAt: attrs['created-at'], source: attrs.source, isConfirmable: attrs['is-confirmable'], hasChanges: attrs['has-changes'], planStatus: attrs['plan-status'] || undefined, applyStatus: attrs['apply-status'] || undefined, workspace: { id: relationships.workspace.data.id, name: attrs['workspace-name'] || 'Unknown' }, configurationVersion: relationships['configuration-version']?.data ? { id: relationships['configuration-version'].data.id, source: attrs['configuration-version-source'] || 'Unknown' } : undefined }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true }; } } ); // Connect to stdio transport const transport = new StdioServerTransport(); await server.connect(transport);

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/matchs/tf-cloud-mcp-server'

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