Terraform Registry MCP Server
by thrashr888
#!/usr/bin/env node
// Polyfill fetch for Node.js versions < 18
import fetch from "node-fetch";
if (!globalThis.fetch) {
globalThis.fetch = fetch as unknown as typeof globalThis.fetch;
}
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
InitializeRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import {
handleProviderLookup,
handleResourceUsage,
handleModuleRecommendations,
handleDataSourceLookup,
handleResourceArgumentDetails,
handleModuleDetails,
handleFunctionDetails,
handleProviderGuides,
handlePolicySearch,
handlePolicyDetails,
handleListOrganizations,
handlePrivateModuleSearch,
handlePrivateModuleDetails,
handleExplorerQuery,
handleListWorkspaces,
handleShowWorkspace,
handleLockWorkspace,
handleUnlockWorkspace,
handleListRuns,
handleShowRun,
handleCreateRun,
handleApplyRun,
handleCancelRun,
handleListWorkspaceResources
} from "./src/tools/index.js";
import { VERSION, SERVER_NAME, TFC_TOKEN } from "./config.js";
import logger from "./src/utils/logger.js";
import {
ProviderLookupInput,
ResourceUsageInput,
ModuleRecommendationsInput,
DataSourceLookupInput,
ResourceDocumentationInput,
ModuleDetailsInput,
FunctionDetailsInput,
ProviderGuidesInput,
PolicySearchInput,
PolicyDetailsInput,
PrivateModuleSearchParams,
PrivateModuleDetailsParams
} from "./src/types/index.js";
import { ExplorerQueryParams } from "./src/tools/explorer.js";
import { WorkspacesQueryParams, WorkspaceActionParams } from "./src/tools/workspaces.js";
import { RunsQueryParams, RunCreateParams, RunActionParams } from "./src/tools/runs.js";
import { WorkspaceResourcesQueryParams } from "./src/tools/workspaceResources.js";
import {
handleResourcesList,
handleResourcesRead,
handleResourcesTemplatesList,
handleResourcesSubscribe
} from "./src/resources/index.js";
// Add a type definition for handleRequest which isn't directly exposed in types
declare module "@modelcontextprotocol/sdk/server/index.js" {
interface Server {
handleRequest(schema: any, request: any): Promise<any>;
}
}
// Define the base tools available in the server
const baseTools: Tool[] = [
{
name: "providerDetails",
description: "Get detailed information about a Terraform provider by name and optionally version.",
inputSchema: {
type: "object",
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
namespace: { type: "string", description: "Provider namespace (e.g. 'hashicorp')" },
version: { type: "string", description: "Provider version (defaults to latest)" }
}
},
handler: handleProviderLookup
},
{
name: "resourceUsage",
description: "Get an example usage of a Terraform resource and related resources.",
inputSchema: {
type: "object",
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
resource: { type: "string", description: "Resource name (e.g. 'aws_instance')" },
name: { type: "string", description: "Alternative resource name field (fallback if resource not specified)" }
}
},
handler: handleResourceUsage
},
{
name: "moduleSearch",
description: "Search for and recommend Terraform modules based on a query.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query (e.g. 'vpc')" },
keyword: { type: "string", description: "Alternative search keyword (fallback if query not specified)" },
provider: { type: "string", description: "Filter modules by provider (e.g. 'aws')" }
}
},
handler: handleModuleRecommendations
},
{
name: "listDataSources",
description: "List all available data sources for a provider and their basic details.",
inputSchema: {
type: "object",
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
namespace: { type: "string", description: "Provider namespace (e.g. 'hashicorp')" }
},
required: ["provider", "namespace"]
},
handler: handleDataSourceLookup
},
{
name: "resourceArgumentDetails",
description:
"Fetches comprehensive details about a specific resource type's arguments, including required and optional attributes, nested blocks, and their descriptions.",
inputSchema: {
type: "object",
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
namespace: { type: "string", description: "Provider namespace (e.g. 'hashicorp')" },
resource: { type: "string", description: "Resource name (e.g. 'aws_instance')" },
version: { type: "string", description: "Provider version (defaults to latest)" }
},
required: ["provider", "namespace", "resource"]
},
handler: handleResourceArgumentDetails
},
{
name: "moduleDetails",
description:
"Retrieves detailed metadata for a Terraform module including versions, inputs, outputs, and dependencies.",
inputSchema: {
type: "object",
properties: {
namespace: { type: "string", description: "Module namespace (e.g. 'terraform-aws-modules')" },
module: { type: "string", description: "Module name (e.g. 'vpc')" },
provider: { type: "string", description: "Provider name (e.g. 'aws')" }
},
required: ["namespace", "module", "provider"]
},
handler: handleModuleDetails
},
{
name: "functionDetails",
description: "Get details about a Terraform provider function.",
inputSchema: {
type: "object",
required: ["provider", "function"],
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
namespace: { type: "string", description: "Provider namespace (e.g. 'hashicorp')" },
function: { type: "string", description: "Function name (e.g. 'arn_parse')" }
}
},
handler: handleFunctionDetails
},
{
name: "providerGuides",
description: "List and view provider-specific guides, including version upgrades and feature guides.",
inputSchema: {
type: "object",
required: ["provider"],
properties: {
provider: { type: "string", description: "Provider name (e.g. 'aws')" },
namespace: { type: "string", description: "Provider namespace (e.g. 'hashicorp')" },
guide: { type: "string", description: "Specific guide to fetch (by slug or title)" },
search: { type: "string", description: "Search term to filter guides" }
}
},
handler: handleProviderGuides
},
{
name: "policySearch",
description: "Search for policy libraries in the Terraform Registry.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query for finding policy libraries" },
provider: { type: "string", description: "Filter policies by provider (e.g. 'aws')" }
}
},
handler: handlePolicySearch
},
{
name: "policyDetails",
description: "Get detailed information about a specific policy library including its latest version.",
inputSchema: {
type: "object",
required: ["namespace", "name"],
properties: {
namespace: { type: "string", description: "Policy library namespace (e.g. 'Great-Stone')" },
name: { type: "string", description: "Policy library name (e.g. 'vault-aws-secret-type')" }
}
},
handler: handlePolicyDetails
}
];
// Define the TFC-specific tools that require authentication
const tfcTools: Tool[] = [
{
name: "listOrganizations",
description: "List all organizations the authenticated user has access to in Terraform Cloud.",
inputSchema: {
type: "object",
properties: {}
},
handler: handleListOrganizations
},
{
name: "privateModuleSearch",
description: "Search for private modules in a Terraform Cloud organization.",
inputSchema: {
type: "object",
required: ["organization"],
properties: {
organization: { type: "string", description: "The organization name to search in" },
query: { type: "string", description: "Search term" },
provider: { type: "string", description: "Filter by provider" },
page: { type: "number", description: "Page number (default: 1)" },
per_page: { type: "number", description: "Results per page (default: 20)" }
}
},
handler: handlePrivateModuleSearch
},
{
name: "privateModuleDetails",
description:
"Get detailed information about a private module including inputs, outputs, and no-code configuration.",
inputSchema: {
type: "object",
required: ["organization", "namespace", "name", "provider"],
properties: {
organization: { type: "string", description: "The organization name" },
namespace: { type: "string", description: "The module namespace, likely same as organization name" },
name: { type: "string", description: "The module name" },
provider: { type: "string", description: "The provider name" },
version: { type: "string", description: "Optional specific version to fetch" }
}
},
handler: handlePrivateModuleDetails
},
{
name: "explorerQuery",
description: "Query the Terraform Cloud Explorer API to analyze data across workspaces in an organization",
inputSchema: {
type: "object",
required: ["organization", "type"],
properties: {
organization: {
type: "string",
description: "The name of the organization to query"
},
type: {
type: "string",
enum: ["workspaces", "tf_versions", "providers", "modules"],
description: "The type of view to query"
},
sort: {
type: "string",
description: "Optional field to sort by (prefix with - for descending)"
},
filter: {
type: "array",
items: {
type: "object",
required: ["field", "operator", "value"],
properties: {
field: { type: "string" },
operator: { type: "string" },
value: { type: "array", items: { type: "string" } }
}
},
description: "Optional filters to apply"
},
fields: {
type: "array",
items: { type: "string" },
description: "Optional specific fields to return"
},
page_number: {
type: "number",
description: "Optional page number"
},
page_size: {
type: "number",
description: "Optional page size"
}
}
},
handler: handleExplorerQuery
},
{
name: "listWorkspaces",
description: "List workspaces in a Terraform Cloud organization",
inputSchema: {
type: "object",
required: ["organization"],
properties: {
organization: {
type: "string",
description: "The name of the organization"
},
page_number: {
type: "number",
description: "Optional page number"
},
page_size: {
type: "number",
description: "Optional page size"
},
include: {
type: "array",
items: { type: "string" },
description: "Optional related resources to include"
}
}
},
handler: handleListWorkspaces
},
{
name: "workspaceDetails",
description: "Get detailed information about a specific workspace in a Terraform Cloud organization",
inputSchema: {
type: "object",
required: ["organization", "name"],
properties: {
organization: {
type: "string",
description: "The name of the organization"
},
name: {
type: "string",
description: "The name of the workspace"
},
include: {
type: "array",
items: { type: "string" },
description: "Optional related resources to include"
}
}
},
handler: handleShowWorkspace
},
{
name: "lockWorkspace",
description: "Lock a workspace to prevent runs",
inputSchema: {
type: "object",
required: ["workspace_id"],
properties: {
workspace_id: {
type: "string",
description: "The ID of the workspace to lock"
},
reason: {
type: "string",
description: "Optional reason for locking"
}
}
},
handler: handleLockWorkspace
},
{
name: "unlockWorkspace",
description: "Unlock a workspace to allow runs",
inputSchema: {
type: "object",
required: ["workspace_id"],
properties: {
workspace_id: {
type: "string",
description: "The ID of the workspace to unlock"
}
}
},
handler: handleUnlockWorkspace
},
{
name: "listRuns",
description: "List runs for a workspace",
inputSchema: {
type: "object",
required: ["workspace_id"],
properties: {
workspace_id: {
type: "string",
description: "The ID of the workspace"
},
page_number: {
type: "number",
description: "Optional page number"
},
page_size: {
type: "number",
description: "Optional page size"
},
include: {
type: "array",
items: { type: "string" },
description: "Optional related resources to include"
}
}
},
handler: handleListRuns
},
{
name: "runDetails",
description: "Get detailed information about a specific run",
inputSchema: {
type: "object",
required: ["run_id"],
properties: {
run_id: {
type: "string",
description: "The ID of the run"
}
}
},
handler: handleShowRun
},
{
name: "createRun",
description: "Create a new run for a workspace",
inputSchema: {
type: "object",
required: ["workspace_id"],
properties: {
workspace_id: {
type: "string",
description: "The ID of the workspace"
},
is_destroy: {
type: "boolean",
description: "Optional destroy flag"
},
message: {
type: "string",
description: "Optional message"
},
auto_apply: {
type: "boolean",
description: "Optional auto-apply setting"
},
refresh: {
type: "boolean",
description: "Optional refresh flag"
},
refresh_only: {
type: "boolean",
description: "Optional refresh-only flag"
},
plan_only: {
type: "boolean",
description: "Optional plan-only flag"
},
terraform_version: {
type: "string",
description: "Optional Terraform version"
}
}
},
handler: handleCreateRun
},
{
name: "applyRun",
description: "Apply a run that's been planned",
inputSchema: {
type: "object",
required: ["run_id"],
properties: {
run_id: {
type: "string",
description: "The ID of the run to apply"
},
comment: {
type: "string",
description: "Optional comment"
}
}
},
handler: handleApplyRun
},
{
name: "cancelRun",
description: "Cancel a run that's in progress",
inputSchema: {
type: "object",
required: ["run_id"],
properties: {
run_id: {
type: "string",
description: "The ID of the run to cancel"
},
comment: {
type: "string",
description: "Optional comment"
}
}
},
handler: handleCancelRun
},
{
name: "listWorkspaceResources",
description: "List resources in a workspace",
inputSchema: {
type: "object",
required: ["workspace_id"],
properties: {
workspace_id: {
type: "string",
description: "The ID of the workspace"
},
page_number: {
type: "number",
description: "Optional page number"
},
page_size: {
type: "number",
description: "Optional page size"
},
filter: {
type: "string",
description: "Optional filter string"
}
}
},
handler: handleListWorkspaceResources
}
];
// Combine tools based on TFC_TOKEN availability
const tools: Tool[] = TFC_TOKEN ? [...baseTools, ...tfcTools] : baseTools;
// Initialize the server
const server = new Server(
{
name: SERVER_NAME,
version: VERSION
},
{
capabilities: {
tools: {},
resources: {
subscribe: true,
listChanged: true
}
}
}
);
// Log initialization
logger.info("Server constructor created, setting up handlers...");
// Initialize handler
server.setRequestHandler(InitializeRequestSchema, async (request) => {
logger.info("Received Initialize request!");
logger.debug("Initialize request details:", request);
return {
protocolVersion: request.params.protocolVersion,
capabilities: { tools: {} },
serverInfo: {
name: SERVER_NAME,
version: VERSION
}
};
});
// ListToolsRequest handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
logger.info("Received ListToolsRequest");
return { tools };
});
// Define resource schemas
const ResourcesListSchema = z.object({
method: z.literal("resources/list"),
params: z.object({
uri: z.string()
})
});
const ResourcesReadSchema = z.object({
method: z.literal("resources/read"),
params: z.object({
uri: z.string()
})
});
const ResourcesTemplatesListSchema = z.object({
method: z.literal("resources/templates/list"),
params: z.object({
uri: z.string()
})
});
const ResourcesSubscribeSchema = z.object({
method: z.literal("resources/subscribe"),
params: z.object({
uri: z.string()
})
});
// Register resources/list handler
server.setRequestHandler(ResourcesListSchema, async (request) => {
logger.info("Received resources/list request!");
const { uri } = request.params;
return await handleResourcesList(uri);
});
// Register resources/read handler
server.setRequestHandler(ResourcesReadSchema, async (request) => {
logger.info("Received resources/read request!");
const { uri } = request.params;
return await handleResourcesRead(uri);
});
// Register resources/templates/list handler
server.setRequestHandler(ResourcesTemplatesListSchema, async () => {
logger.info("Received resources/templates/list request!");
return await handleResourcesTemplatesList();
});
// Register resources/subscribe handler
server.setRequestHandler(ResourcesSubscribeSchema, async () => {
logger.info("Received resources/subscribe request!");
return await handleResourcesSubscribe();
});
// Validate and convert arguments
function validateArgs<T>(args: Record<string, unknown> | undefined, requiredFields: string[]): T | undefined {
if (!args) return undefined;
for (const field of requiredFields) {
if (!(field in args)) {
throw new Error(`Missing required field: ${field}`);
}
}
return args as T;
}
// Handle tool requests
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name: toolName, arguments: args } = request.params;
if (!toolName) {
return {
content: [
{
type: "text",
text: JSON.stringify({
status: "error",
error: "Tool name is required"
})
}
]
};
}
try {
let response;
switch (toolName) {
case "resourceArgumentDetails": {
const validArgs = validateArgs<ResourceDocumentationInput>(args, ["namespace", "provider", "resource"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleResourceArgumentDetails(validArgs);
break;
}
case "resourceUsage": {
const validArgs = validateArgs<ResourceUsageInput>(args, ["provider", "resource"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleResourceUsage(validArgs);
break;
}
case "providerDetails": {
const validArgs = validateArgs<ProviderLookupInput>(args, ["provider"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleProviderLookup(validArgs);
break;
}
case "moduleSearch": {
const validArgs = validateArgs<ModuleRecommendationsInput>(args, ["query"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleModuleRecommendations(validArgs);
break;
}
case "listDataSources": {
const validArgs = validateArgs<DataSourceLookupInput>(args, ["provider", "namespace"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleDataSourceLookup(validArgs);
break;
}
case "moduleDetails": {
const validArgs = validateArgs<ModuleDetailsInput>(args, ["namespace", "module", "provider"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleModuleDetails(validArgs);
break;
}
case "functionDetails": {
const validArgs = validateArgs<FunctionDetailsInput>(args, ["provider", "function"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleFunctionDetails(validArgs);
break;
}
case "providerGuides": {
const validArgs = validateArgs<ProviderGuidesInput>(args, ["provider"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleProviderGuides(validArgs);
break;
}
case "policySearch": {
const validArgs = validateArgs<PolicySearchInput>(args, ["query"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handlePolicySearch(validArgs);
break;
}
case "policyDetails": {
const validArgs = validateArgs<PolicyDetailsInput>(args, ["namespace", "name"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handlePolicyDetails(validArgs);
break;
}
case "listOrganizations": {
response = await handleListOrganizations();
break;
}
case "privateModuleSearch": {
const validArgs = validateArgs<PrivateModuleSearchParams>(args, ["organization"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handlePrivateModuleSearch(validArgs);
break;
}
case "privateModuleDetails": {
const validArgs = validateArgs<PrivateModuleDetailsParams>(args, [
"organization",
"namespace",
"name",
"provider"
]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handlePrivateModuleDetails(validArgs);
break;
}
case "explorerQuery": {
const validArgs = validateArgs<ExplorerQueryParams>(args, ["organization", "type"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleExplorerQuery(validArgs);
break;
}
case "listWorkspaces": {
const validArgs = validateArgs<WorkspacesQueryParams>(args, ["organization"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleListWorkspaces(validArgs);
break;
}
case "workspaceDetails": {
const validArgs = validateArgs<Record<string, any>>(args, ["organization", "name"]);
if (!validArgs) throw new Error("Missing required arguments");
// Convert from the API parameter names to the internal parameter names
const params = {
organization_name: validArgs.organization,
name: validArgs.name
};
response = await handleShowWorkspace(params);
break;
}
case "lockWorkspace": {
const validArgs = validateArgs<WorkspaceActionParams>(args, ["workspace_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleLockWorkspace(validArgs);
break;
}
case "unlockWorkspace": {
const validArgs = validateArgs<WorkspaceActionParams>(args, ["workspace_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleUnlockWorkspace(validArgs);
break;
}
case "listRuns": {
const validArgs = validateArgs<RunsQueryParams>(args, ["workspace_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleListRuns(validArgs);
break;
}
case "runDetails": {
const validArgs = validateArgs<RunActionParams>(args, ["run_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleShowRun(validArgs);
break;
}
case "createRun": {
const validArgs = validateArgs<RunCreateParams>(args, ["workspace_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleCreateRun(validArgs);
break;
}
case "applyRun": {
const validArgs = validateArgs<RunActionParams>(args, ["run_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleApplyRun(validArgs);
break;
}
case "cancelRun": {
const validArgs = validateArgs<RunActionParams>(args, ["run_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleCancelRun(validArgs);
break;
}
case "listWorkspaceResources": {
const validArgs = validateArgs<WorkspaceResourcesQueryParams>(args, ["workspace_id"]);
if (!validArgs) throw new Error("Missing required arguments");
response = await handleListWorkspaceResources(validArgs);
break;
}
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return response;
} catch (error) {
logger.error("Error handling tool request:", error);
return {
content: [
{
type: "text",
text: JSON.stringify({
status: "error",
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
// Start the server
async function main() {
console.error("🚀 Starting terraform-registry MCP server...");
const transport = new StdioServerTransport();
// Prevent unhandled promise rejections from crashing the server
process.on("unhandledRejection", (reason) => {
console.error("💥 Unhandled Promise Rejection:", reason);
});
try {
await server.connect(transport);
console.error("✅ Server connected and ready for requests");
console.error("📝 Server running on stdio transport");
} catch (error) {
console.error("❌ Fatal error:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("💀 Fatal error:", error);
process.exit(1);
});