Skip to main content
Glama

OpenTofu MCP Server

Official
by opentofu
index.ts5.37 kB
import semver from "semver"; import type { components, operations } from "../generated/opentofu-api.js"; import { PACKAGE_NAME, PACKAGE_VERSION } from "../utils.js"; type apiDefinition = components["schemas"]; export const API_BASE_URL = "https://api.opentofu.org"; export type ProviderWithLatestVersion = apiDefinition["Provider"] & { latestVersion?: apiDefinition["ProviderVersion"]; }; export type DocType = operations["GetProviderDocItem"]["parameters"]["path"]["kind"]; export class RegistryClient { private apiBaseUrl: string; private userAgent = `${PACKAGE_NAME}/${PACKAGE_VERSION}`; private fetch: typeof globalThis.fetch; constructor(apiBaseUrl: string = API_BASE_URL, fetch: typeof globalThis.fetch = globalThis.fetch) { this.apiBaseUrl = apiBaseUrl; this.fetch = fetch; } private async fetchFromApi<T>(path: string, params: Record<string, string> = {}, responseType: "json" | "text" = "json"): Promise<T> { const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== undefined) { queryParams.append(key, value); } } const queryString = queryParams.toString(); const url = `${this.apiBaseUrl}${String(path)}${queryString ? `?${queryString}` : ""}`; const response = await this.fetch(url, { headers: { "User-Agent": this.userAgent, }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } return (responseType === "json" ? response.json() : response.text()) as Promise<T>; } private getLatestVersion(versions: apiDefinition["ProviderVersionDescriptor"][]): string | undefined { if (!versions || versions.length === 0) { return undefined; } const validVersions = versions .map((v) => ({ original: v.id, normalized: semver.valid(semver.coerce(v.id.replace(/^v/, ""))), })) .filter((v) => v.normalized !== null); if (validVersions.length === 0) { return versions[0].id; } validVersions.sort((a, b) => { return semver.compare(b.normalized as string, a.normalized as string); }); return validVersions[0].original; } private async getLatestProviderVersion(namespace: string, name: string): Promise<string | undefined> { const provider = await this.fetchFromApi<apiDefinition["Provider"]>(`/registry/docs/providers/${namespace}/${name}/index.json`); return this.getLatestVersion(provider.versions); } async search(query: string, type?: string): Promise<apiDefinition["SearchResultItem"][]> { const params: Record<string, string> = { q: query }; const response = await this.fetchFromApi<apiDefinition["SearchResultItem"][]>("/registry/docs/search", params); let results = response; if (type && type !== "all") { results = response.filter((item) => item.type.toLowerCase() === type.toLowerCase()); } return results; } async getProviderList(): Promise<apiDefinition["ProviderList"]> { return await this.fetchFromApi<apiDefinition["ProviderList"]>("/registry/docs/providers/index.json"); } async getProviderDetails(namespace: string, name: string): Promise<ProviderWithLatestVersion> { const path = `/registry/docs/providers/${namespace}/${name}/index.json`; const provider = await this.fetchFromApi<apiDefinition["Provider"]>(path); const enhancedProvider: ProviderWithLatestVersion = { ...provider }; if (provider.versions?.length > 0) { const latestVersion = this.getLatestVersion(provider.versions); const versionPath = `/registry/docs/providers/${namespace}/${name}/${latestVersion}/index.json`; const versionDetails = await this.fetchFromApi<apiDefinition["ProviderVersion"]>(versionPath); enhancedProvider.latestVersion = versionDetails; } return enhancedProvider; } async getModuleList(): Promise<apiDefinition["ModuleList"]> { return await this.fetchFromApi<apiDefinition["ModuleList"]>("/registry/docs/modules/index.json"); } async getModuleDetails(namespace: string, name: string, target: string): Promise<apiDefinition["Module"]> { const path = `/registry/docs/modules/${namespace}/${name}/${target}/index.json`; return await this.fetchFromApi<apiDefinition["Module"]>(path); } async getResourceDocs(namespace: string, name: string, target: string, version?: string): Promise<string> { return await this.fetchMarkdownDoc("resource", namespace, name, target, version); } async getDataSourceDocs(namespace: string, name: string, target: string, version?: string): Promise<string> { return await this.fetchMarkdownDoc("datasource", namespace, name, target, version); } private async fetchMarkdownDoc(docType: DocType, namespace: string, name: string, docName: string, version?: string): Promise<string> { let targetVersion = version; if (!targetVersion) { targetVersion = await this.getLatestProviderVersion(namespace, name); if (!targetVersion) { throw new Error(`Could not determine latest version for provider ${namespace}/${name}`); } } const path = `/registry/docs/providers/${namespace}/${name}/${targetVersion}/${docType}s/${docName}.md`; console.error(`Fetching ${path}`); return this.fetchFromApi<string>(path, {}, "text"); } }

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/opentofu/opentofu-mcp-server'

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