Skip to main content
Glama
tools.ts26.5 kB
import { z } from 'zod'; import type { InfracostConfig } from './types.js'; import { executeBreakdown, executeDiff, executeOutput, executeUpload, executeComment, checkInfracostInstalled, } from './cli.js'; import { InfracostCloudAPIClient } from './api.js'; export const BreakdownSchema = z.object({ path: z.string().describe('Path to Terraform directory or plan JSON file'), format: z.enum(['json', 'table', 'html']).optional().describe('Output format'), outFile: z.string().optional().describe('Save output to file'), terraformVarFile: z.array(z.string()).optional().describe('Terraform var file paths'), terraformVar: z.record(z.string()).optional().describe('Terraform variables as key-value pairs'), }); export const DiffSchema = z.object({ path: z.string().describe('Path to Terraform directory or plan JSON file'), compareTo: z.string().describe('Path to baseline Terraform directory or plan JSON file'), format: z.enum(['json', 'diff']).optional().describe('Output format'), outFile: z.string().optional().describe('Save output to file'), }); export const OutputSchema = z.object({ path: z.string().describe('Path to Infracost JSON file(s)'), format: z .enum([ 'json', 'table', 'html', 'diff', 'github-comment', 'gitlab-comment', 'azure-repos-comment', 'bitbucket-comment', ]) .optional() .describe('Output format'), outFile: z.string().optional().describe('Save output to file'), fields: z.array(z.string()).optional().describe('Fields to include in output'), showSkipped: z.boolean().optional().describe('Show skipped resources'), }); export const UploadSchema = z.object({ path: z.string().describe('Path to Infracost JSON file'), }); export const CommentSchema = z.object({ path: z.string().describe('Path to Infracost JSON file'), platform: z.enum(['github', 'gitlab', 'azure-repos', 'bitbucket']).describe('Git platform'), repo: z.string().optional().describe('Repository in format owner/repo'), pullRequest: z.string().optional().describe('Pull request number'), commit: z.string().optional().describe('Commit SHA'), tag: z.string().optional().describe('Tag for comment identification'), behavior: z .enum(['update', 'new', 'delete-and-new']) .optional() .describe('Comment behavior'), }); export const UpdateTaggingPolicySchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), policyId: z.string().describe('Policy ID from the URL in Infracost Cloud UI'), name: z.string().optional().describe('Name for the tagging policy'), message: z.string().optional().describe('The message to display in the PR comment'), prComment: z.boolean().optional().describe('Whether to add a comment to the PR'), blockPr: z.boolean().optional().describe('Whether to block the PR'), tags: z .array( z.object({ key: z.string().describe('Tag key name'), mandatory: z.boolean().describe('Whether the tag is required'), valueType: z.enum(['ANY', 'LIST', 'REGEX']).describe('Value type: ANY (any value), LIST (predefined values), or REGEX (regex pattern)'), allowedValues: z.array(z.string()).optional().describe('List of allowed values (for LIST type)'), allowedRegex: z.string().optional().describe('Regex pattern for allowed values (for REGEX type)'), message: z.string().optional().describe('Optional message to display if the tag is missing/invalid'), }) ) .optional() .describe('Array of tag definitions'), filters: z .object({ repos: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Repository filters'), projects: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Project filters'), baseBranches: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Base branch filters'), resources: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Resource filters'), }) .optional() .describe('Filters to limit scope of the policy'), }); export const CreateGuardrailSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), name: z.string().describe('Name for the guardrail'), scope: z .object({ type: z .enum(['ALL_PROJECTS', 'REPO', 'PROJECT']) .describe('Scope type for the guardrail'), repositories: z.array(z.string()).optional().describe('Repository names (for REPO scope)'), projects: z.array(z.string()).optional().describe('Project names (for PROJECT scope)'), }) .describe('Scope configuration'), increaseThreshold: z .number() .optional() .describe('Threshold for cost increases (monthly dollar amount)'), increasePercentThreshold: z .number() .optional() .describe('Threshold for cost increases (percentage)'), totalThreshold: z .number() .optional() .describe('Threshold for total cost (monthly dollar amount)'), message: z.string().optional().describe('Custom message to display when threshold is exceeded'), webhookUrl: z.string().default('').describe('Webhook URL to notify when threshold is exceeded (defaults to empty string if not needed)'), blockPullRequest: z.boolean().optional().describe('Whether to block PR when threshold is exceeded'), commentOnPullRequest: z .boolean() .optional() .describe('Whether to comment on PR when threshold is exceeded'), emailRecipientOrgMemberIds: z .array(z.string()) .optional() .describe('Array of organization member IDs to email'), mailingListEmails: z .array(z.string()) .optional() .describe('Array of email addresses to notify'), msTeamsEmails: z .array(z.string()) .optional() .describe('Array of MS Teams email addresses to notify'), }); export const UploadCustomPropertiesSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), csvData: z.string().describe('CSV data containing custom properties'), }); // New tagging policy schemas export const ListTaggingPoliciesSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), }); export const GetTaggingPolicySchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), policyId: z.string().describe('Policy ID'), }); export const CreateTaggingPolicySchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), name: z.string().describe('Name for the tagging policy'), message: z.string().optional().describe('The message to display in the PR comment'), prComment: z.boolean().optional().describe('Whether to add a comment to the PR'), blockPr: z.boolean().optional().describe('Whether to block the PR'), tags: z .array( z.object({ key: z.string().describe('Tag key name'), mandatory: z.boolean().describe('Whether the tag is required'), valueType: z.enum(['ANY', 'LIST', 'REGEX']).describe('Value type: ANY (any value), LIST (predefined values), or REGEX (regex pattern)'), allowedValues: z.array(z.string()).optional().describe('List of allowed values (for LIST type)'), allowedRegex: z.string().optional().describe('Regex pattern for allowed values (for REGEX type)'), message: z.string().optional().describe('Optional message to display if the tag is missing/invalid'), }) ) .describe('Array of tag definitions'), filters: z .object({ repos: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Repository filters'), projects: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Project filters'), baseBranches: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Base branch filters'), resources: z.object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }).optional().describe('Resource filters'), }) .optional() .describe('Filters to limit scope of the policy'), }); export const DeleteTaggingPolicySchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), policyId: z.string().describe('Policy ID to delete'), }); export const ListGuardrailsSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), }); export const GetGuardrailSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), guardrailId: z.string().describe('Guardrail ID'), }); export const UpdateGuardrailSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), guardrailId: z.string().describe('Guardrail ID'), name: z.string().optional().describe('Name for the guardrail'), scope: z .object({ type: z .enum(['ALL_PROJECTS', 'REPO', 'PROJECT']) .describe('Scope type for the guardrail'), repositories: z.array(z.string()).optional().describe('Repository names (for REPO scope)'), projects: z.array(z.string()).optional().describe('Project names (for PROJECT scope)'), }) .optional() .describe('Scope configuration'), increaseThreshold: z .number() .optional() .describe('Threshold for cost increases (monthly dollar amount)'), increasePercentThreshold: z .number() .optional() .describe('Threshold for cost increases (percentage)'), totalThreshold: z .number() .optional() .describe('Threshold for total cost (monthly dollar amount)'), message: z.string().optional().describe('Custom message to display when threshold is exceeded'), webhookUrl: z.string().default('').describe('Webhook URL to notify when threshold is exceeded (defaults to empty string if not needed)'), blockPullRequest: z.boolean().optional().describe('Whether to block PR when threshold is exceeded'), commentOnPullRequest: z .boolean() .optional() .describe('Whether to comment on PR when threshold is exceeded'), emailRecipientOrgMemberIds: z .array(z.string()) .optional() .describe('Array of organization member IDs to email'), mailingListEmails: z .array(z.string()) .optional() .describe('Array of email addresses to notify'), msTeamsEmails: z .array(z.string()) .optional() .describe('Array of MS Teams email addresses to notify'), }); export const DeleteGuardrailSchema = z.object({ orgSlug: z.string().optional().describe('Organization slug from Infracost Cloud (defaults to INFRACOST_ORG env var)'), guardrailId: z.string().describe('Guardrail ID to delete'), }); export class InfracostTools { private config: InfracostConfig; private cloudApiClient?: InfracostCloudAPIClient; constructor(config: InfracostConfig) { this.config = config; if (config.serviceToken) { this.cloudApiClient = new InfracostCloudAPIClient(config); } } async handleBreakdown(args: z.infer<typeof BreakdownSchema>) { const isInstalled = await checkInfracostInstalled(); if (!isInstalled) { throw new Error( 'Infracost CLI is not installed. Please install it from https://www.infracost.io/docs/' ); } const result = await executeBreakdown(args); if (!result.success) { throw new Error(result.error || 'Breakdown command failed'); } return { content: [ { type: 'text', text: result.output || 'Breakdown completed successfully', }, ], }; } async handleDiff(args: z.infer<typeof DiffSchema>) { const isInstalled = await checkInfracostInstalled(); if (!isInstalled) { throw new Error( 'Infracost CLI is not installed. Please install it from https://www.infracost.io/docs/' ); } const result = await executeDiff(args); if (!result.success) { throw new Error(result.error || 'Diff command failed'); } return { content: [ { type: 'text', text: result.output || 'Diff completed successfully', }, ], }; } async handleOutput(args: z.infer<typeof OutputSchema>) { const isInstalled = await checkInfracostInstalled(); if (!isInstalled) { throw new Error( 'Infracost CLI is not installed. Please install it from https://www.infracost.io/docs/' ); } const result = await executeOutput(args); if (!result.success) { throw new Error(result.error || 'Output command failed'); } return { content: [ { type: 'text', text: result.output || 'Output generated successfully', }, ], }; } async handleUpload(args: z.infer<typeof UploadSchema>) { const isInstalled = await checkInfracostInstalled(); if (!isInstalled) { throw new Error( 'Infracost CLI is not installed. Please install it from https://www.infracost.io/docs/' ); } const result = await executeUpload(args); if (!result.success) { throw new Error(result.error || 'Upload command failed'); } return { content: [ { type: 'text', text: result.output || 'Upload completed successfully', }, ], }; } async handleComment(args: z.infer<typeof CommentSchema>) { const isInstalled = await checkInfracostInstalled(); if (!isInstalled) { throw new Error( 'Infracost CLI is not installed. Please install it from https://www.infracost.io/docs/' ); } const result = await executeComment(args); if (!result.success) { throw new Error(result.error || 'Comment command failed'); } return { content: [ { type: 'text', text: result.output || 'Comment posted successfully', }, ], }; } async handleUpdateTaggingPolicy(args: z.infer<typeof UpdateTaggingPolicySchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { policyId, orgSlug: _, ...updateData } = args; const result = await this.cloudApiClient.updateTaggingPolicy(orgSlug, policyId, updateData); if (!result.success) { throw new Error(result.error || 'Update tagging policy request failed'); } return { content: [ { type: 'text', text: result.output || 'Tagging policy updated successfully', }, ], }; } async handleCreateGuardrail(args: z.infer<typeof CreateGuardrailSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { orgSlug: _, scope, blockPullRequest, commentOnPullRequest, webhookUrl, emailRecipientOrgMemberIds, mailingListEmails, msTeamsEmails, ...rest } = args; let scopeString: string; if (scope.type === 'ALL_PROJECTS' || scope.type === 'REPO') { scopeString = 'REPO'; } else { scopeString = 'PROJECT'; } const filters: any = {}; if (scope.repositories && scope.repositories.length > 0) { filters.repos = { include: scope.repositories }; } if (scope.projects && scope.projects.length > 0) { filters.projects = { include: scope.projects }; } const apiRequest: any = { ...rest, scope: scopeString, filters: Object.keys(filters).length > 0 ? filters : undefined, prComment: commentOnPullRequest, blockPr: blockPullRequest, webhookUrl, // Schema defaults to empty string if not provided }; if (emailRecipientOrgMemberIds) { apiRequest.emailRecipientOrgMemberIds = emailRecipientOrgMemberIds; } if (mailingListEmails) { apiRequest.mailingListEmails = mailingListEmails; } if (msTeamsEmails) { apiRequest.msTeamsEmails = msTeamsEmails; } const result = await this.cloudApiClient.createGuardrail(orgSlug, apiRequest); if (!result.success) { throw new Error(result.error || 'Create guardrail request failed'); } return { content: [ { type: 'text', text: result.output || 'Guardrail created successfully', }, ], }; } async handleUploadCustomProperties(args: z.infer<typeof UploadCustomPropertiesSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { csvData } = args; const result = await this.cloudApiClient.uploadCustomProperties(orgSlug, { csvData }); if (!result.success) { throw new Error(result.error || 'Upload custom properties request failed'); } return { content: [ { type: 'text', text: result.output || 'Custom properties uploaded successfully', }, ], }; } async handleListTaggingPolicies(args: z.infer<typeof ListTaggingPoliciesSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const result = await this.cloudApiClient.listTaggingPolicies(orgSlug); if (!result.success) { throw new Error(result.error || 'List tagging policies request failed'); } return { content: [ { type: 'text', text: result.output || 'Tagging policies retrieved successfully', }, ], }; } async handleGetTaggingPolicy(args: z.infer<typeof GetTaggingPolicySchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { policyId } = args; const result = await this.cloudApiClient.getTaggingPolicy(orgSlug, policyId); if (!result.success) { throw new Error(result.error || 'Get tagging policy request failed'); } return { content: [ { type: 'text', text: result.output || 'Tagging policy retrieved successfully', }, ], }; } async handleCreateTaggingPolicy(args: z.infer<typeof CreateTaggingPolicySchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { orgSlug: _, ...policyData } = args; const result = await this.cloudApiClient.createTaggingPolicy(orgSlug, policyData); if (!result.success) { throw new Error(result.error || 'Create tagging policy request failed'); } return { content: [ { type: 'text', text: result.output || 'Tagging policy created successfully', }, ], }; } async handleDeleteTaggingPolicy(args: z.infer<typeof DeleteTaggingPolicySchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { policyId } = args; const result = await this.cloudApiClient.deleteTaggingPolicy(orgSlug, policyId); if (!result.success) { throw new Error(result.error || 'Delete tagging policy request failed'); } return { content: [ { type: 'text', text: result.output || 'Tagging policy deleted successfully', }, ], }; } async handleListGuardrails(args: z.infer<typeof ListGuardrailsSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const result = await this.cloudApiClient.listGuardrails(orgSlug); if (!result.success) { throw new Error(result.error || 'List guardrails request failed'); } return { content: [ { type: 'text', text: result.output || 'Guardrails retrieved successfully', }, ], }; } async handleGetGuardrail(args: z.infer<typeof GetGuardrailSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { guardrailId } = args; const result = await this.cloudApiClient.getGuardrail(orgSlug, guardrailId); if (!result.success) { throw new Error(result.error || 'Get guardrail request failed'); } return { content: [ { type: 'text', text: result.output || 'Guardrail retrieved successfully', }, ], }; } async handleUpdateGuardrail(args: z.infer<typeof UpdateGuardrailSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { orgSlug: _, guardrailId, scope, blockPullRequest, commentOnPullRequest, emailRecipientOrgMemberIds, mailingListEmails, msTeamsEmails, ...rest } = args; const apiRequest: any = { ...rest, }; if (scope) { if (scope.type === 'ALL_PROJECTS' || scope.type === 'REPO') { apiRequest.scope = 'REPO'; } else { apiRequest.scope = 'PROJECT'; } const filters: any = {}; if (scope.repositories && scope.repositories.length > 0) { filters.repos = { include: scope.repositories }; } if (scope.projects && scope.projects.length > 0) { filters.projects = { include: scope.projects }; } if (Object.keys(filters).length > 0) { apiRequest.filters = filters; } } if (commentOnPullRequest !== undefined) { apiRequest.prComment = commentOnPullRequest; } if (blockPullRequest !== undefined) { apiRequest.blockPr = blockPullRequest; } if (emailRecipientOrgMemberIds !== undefined) { apiRequest.emailRecipientOrgMemberIds = emailRecipientOrgMemberIds; } if (mailingListEmails !== undefined) { apiRequest.mailingListEmails = mailingListEmails; } if (msTeamsEmails !== undefined) { apiRequest.msTeamsEmails = msTeamsEmails; } const result = await this.cloudApiClient.updateGuardrail(orgSlug, guardrailId, apiRequest); if (!result.success) { throw new Error(result.error || 'Update guardrail request failed'); } return { content: [ { type: 'text', text: result.output || 'Guardrail updated successfully', }, ], }; } async handleDeleteGuardrail(args: z.infer<typeof DeleteGuardrailSchema>) { if (!this.cloudApiClient) { throw new Error('INFRACOST_SERVICE_TOKEN is not configured for Infracost Cloud API operations'); } const orgSlug = args.orgSlug || this.config.orgSlug; if (!orgSlug) { throw new Error('Organization slug is required. Provide it via orgSlug parameter or set INFRACOST_ORG environment variable'); } const { guardrailId } = args; const result = await this.cloudApiClient.deleteGuardrail(orgSlug, guardrailId); if (!result.success) { throw new Error(result.error || 'Delete guardrail request failed'); } return { content: [ { type: 'text', text: result.output || 'Guardrail deleted successfully', }, ], }; } }

Latest Blog Posts

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/phildougherty/infracost_mcp'

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