UNPKG

finops-mcp-server

Version:

MCP server for FinOps Center cost optimization integration

1 lines 9.52 kB
import{Command}from"commander";import{LogLevel}from"./types/config.js";const DEFAULT_CONFIG={timeout:3e4,retryAttempts:3,logLevel:LogLevel.INFO,maxConcurrentRequests:10,requestQueueSize:100,cacheEnabled:!1,cacheTTL:3e5,validateSSL:!0,rateLimitEnabled:!1,rateLimitRequests:100,rateLimitWindow:6e4},ENVIRONMENT_CONFIGS={development:{name:"development",apiUrl:"https://dev-api.finops-center.com/graphql",description:"Development environment for testing",timeout:15e3,retryAttempts:2,rateLimitRequests:50,rateLimitWindow:6e4},staging:{name:"staging",apiUrl:"https://staging-api.finops-center.com/graphql",description:"Staging environment for pre-production testing",timeout:25e3,retryAttempts:3,rateLimitRequests:75,rateLimitWindow:6e4},production:{name:"production",apiUrl:"https://api.finops-center.com/graphql",description:"Production environment",timeout:3e4,retryAttempts:3,rateLimitRequests:100,rateLimitWindow:6e4}};export class CommandLineParser{constructor(){this.program=new Command,this.setupCommands()}setupCommands(){this.program.name("finops-mcp-server").description("FinOps MCP Server - Model Context Protocol server for FinOps Center cost optimization integration").version("1.0.0").option("--api-url <url>","FinOps Center API endpoint URL (required)").option("--api-key <key>","API key for authentication (required)").option("--customer-id <id>","Customer ID for tenant identification").option("--environment <env>","Environment configuration (development, staging, production)").option("--timeout <ms>","Request timeout in milliseconds",e=>parseInt(e,10),DEFAULT_CONFIG.timeout).option("--retry-attempts <count>","Number of retry attempts for failed requests",e=>parseInt(e,10),DEFAULT_CONFIG.retryAttempts).option("--log-level <level>","Logging level (debug, info, warn, error, silent)").option("--max-concurrent-requests <count>","Maximum concurrent API requests",e=>parseInt(e,10),DEFAULT_CONFIG.maxConcurrentRequests).option("--cache-enabled","Enable response caching",DEFAULT_CONFIG.cacheEnabled).option("--cache-ttl <ms>","Cache TTL in milliseconds",e=>parseInt(e,10),DEFAULT_CONFIG.cacheTTL).option("--validate-ssl","Validate SSL certificates",DEFAULT_CONFIG.validateSSL).option("--no-validate-ssl","Disable SSL certificate validation").option("--rate-limit-enabled","Enable rate limiting",DEFAULT_CONFIG.rateLimitEnabled).option("--rate-limit-requests <count>","Rate limit requests per window",e=>parseInt(e,10),DEFAULT_CONFIG.rateLimitRequests).option("--rate-limit-window <ms>","Rate limit window in milliseconds",e=>parseInt(e,10),DEFAULT_CONFIG.rateLimitWindow).option("--stdio","Use stdio for MCP communication (default)",!0).helpOption("-h, --help","Display help information").exitOverride().allowUnknownOption(!1).addHelpText("after",'\nExamples:\n $ finops-mcp-server --api-url="https://api.xyz.com/graphql" --api-key="finops_abc123def456"\n $ finops-mcp-server --api-url="https://finops-api.fidelity.com/graphql" --api-key="finops_abc123def456" --customer-id="customer-123" --environment="production"\n $ finops-mcp-server --api-url="https://cost-api.acme-corp.com/graphql" --api-key="finops_dev123" --environment="development" --log-level="debug"\n\nEnvironment Variables:\n FINOPS_API_URL - API endpoint URL (overrides --api-url)\n FINOPS_API_KEY - API key (overrides --api-key)\n FINOPS_CUSTOMER_ID - Customer ID (overrides --customer-id)\n FINOPS_ENVIRONMENT - Environment (overrides --environment)\n FINOPS_LOG_LEVEL - Log level (overrides --log-level)\n\nFor more information, visit: https://github.com/finops-center/finops-mcp-server\n ')}parse(e){try{const t=e||process.argv;(t.includes("--help")||t.includes("-h"))&&(this.program.help(),process.exit(0)),(t.includes("--version")||t.includes("-V"))&&process.exit(0),this.program.parse(e);const i=this.program.opts();return{"api-url":i.apiUrl||process.env.FINOPS_API_URL,"api-key":i.apiKey||process.env.FINOPS_API_KEY,"customer-id":i.customerId||process.env.FINOPS_CUSTOMER_ID,environment:i.environment||process.env.FINOPS_ENVIRONMENT||"production",timeout:i.timeout,"retry-attempts":i.retryAttempts,"log-level":i.logLevel||process.env.FINOPS_LOG_LEVEL,"max-concurrent-requests":i.maxConcurrentRequests,"cache-enabled":i.cacheEnabled,"cache-ttl":i.cacheTtl,"validate-ssl":i.validateSsl,"rate-limit-enabled":i.rateLimitEnabled,"rate-limit-requests":i.rateLimitRequests,"rate-limit-window":i.rateLimitWindow,stdio:i.stdio,help:i.help,version:i.version}}catch(e){e instanceof Error&&(e.message.includes("help")&&(this.program.help(),process.exit(0)),e.message.includes("version")&&process.exit(0),this.program.help(),process.exit(1));const t=e instanceof Error?e.message:String(e);throw new Error(`Failed to parse command-line arguments: ${t}`)}}showHelp(){this.program.help()}}export class ConfigValidator{static validateArgs(e){const t=[];return e["api-url"]||t.push({field:"api-url",message:"API URL is required. Use --api-url or set FINOPS_API_URL environment variable.",value:e["api-url"]}),e["api-key"]||t.push({field:"api-key",message:"API key is required. Use --api-key or set FINOPS_API_KEY environment variable.",value:e["api-key"]}),e["api-url"]&&!this.isValidUrl(e["api-url"])&&t.push({field:"api-url",message:"API URL must be a valid HTTPS URL.",value:e["api-url"]}),e["api-key"]&&!this.isValidApiKey(e["api-key"])&&t.push({field:"api-key",message:'API key must start with "finops_" and be at least 20 characters long.',value:e["api-key"]?`${e["api-key"].substring(0,10)}...`:void 0}),e.environment&&!this.isValidEnvironment(e.environment)&&t.push({field:"environment",message:"Environment must be one of: development, staging, production.",value:e.environment}),void 0!==e.timeout&&(e.timeout<1e3||e.timeout>3e5)&&t.push({field:"timeout",message:"Timeout must be between 1000ms and 300000ms (5 minutes).",value:e.timeout}),void 0!==e["retry-attempts"]&&(e["retry-attempts"]<0||e["retry-attempts"]>10)&&t.push({field:"retry-attempts",message:"Retry attempts must be between 0 and 10.",value:e["retry-attempts"]}),e["log-level"]&&!this.isValidLogLevel(e["log-level"])&&t.push({field:"log-level",message:"Log level must be one of: debug, info, warn, error, silent.",value:e["log-level"]}),{isValid:0===t.length,errors:t}}static validateConfig(e){const t=[];return e.apiUrl||t.push({field:"apiUrl",message:"API URL is required.",value:e.apiUrl}),e.apiKey||t.push({field:"apiKey",message:"API key is required.",value:e.apiKey}),e.apiUrl&&!this.isValidUrl(e.apiUrl)&&t.push({field:"apiUrl",message:"API URL must be a valid HTTPS URL.",value:e.apiUrl}),{isValid:0===t.length,errors:t}}static isValidUrl(e){try{const t=new URL(e);return"https:"===t.protocol&&t.hostname.length>0}catch{return!1}}static isValidApiKey(e){return e.startsWith("finops_")&&e.length>=20}static isValidEnvironment(e){return["development","staging","production"].includes(e)}static isValidLogLevel(e){return Object.values(LogLevel).includes(e)}}export class ServerConfigBuilder{constructor(){this.config={}}fromCommandLine(e){return e["api-url"]&&(this.config.apiUrl=e["api-url"]),e["api-key"]&&(this.config.apiKey=e["api-key"]),e["customer-id"]&&(this.config.customerId=e["customer-id"]),e.environment&&(this.config.environment=e.environment),e.timeout&&(this.config.timeout=e.timeout),e["retry-attempts"]&&(this.config.retryAttempts=e["retry-attempts"]),e["log-level"]&&(this.config.logLevel=e["log-level"]),e["max-concurrent-requests"]&&(this.config.maxConcurrentRequests=e["max-concurrent-requests"]),void 0!==e["cache-enabled"]&&(this.config.cacheEnabled=e["cache-enabled"]),e["cache-ttl"]&&(this.config.cacheTTL=e["cache-ttl"]),void 0!==e["validate-ssl"]&&(this.config.validateSSL=e["validate-ssl"]),void 0!==e["rate-limit-enabled"]&&(this.config.rateLimitEnabled=e["rate-limit-enabled"]),e["rate-limit-requests"]&&(this.config.rateLimitRequests=e["rate-limit-requests"]),e["rate-limit-window"]&&(this.config.rateLimitWindow=e["rate-limit-window"]),this}fromEnvironment(e){const t=ENVIRONMENT_CONFIGS[e];return t&&(this.config.apiUrl||(this.config.apiUrl=t.apiUrl),!this.config.timeout&&t.timeout&&(this.config.timeout=t.timeout),!this.config.retryAttempts&&t.retryAttempts&&(this.config.retryAttempts=t.retryAttempts),!this.config.rateLimitRequests&&t.rateLimitRequests&&(this.config.rateLimitRequests=t.rateLimitRequests),!this.config.rateLimitWindow&&t.rateLimitWindow&&(this.config.rateLimitWindow=t.rateLimitWindow)),this}withDefaults(e=DEFAULT_CONFIG){return Object.keys(e).forEach(t=>{void 0===this.config[t]&&(this.config[t]=e[t])}),this}validate(){return ConfigValidator.validateConfig(this.config)}build(){const e=this.validate();if(!e.isValid){const t=e.errors.map(e=>`${e.field}: ${e.message}`).join("\n");throw new Error(`Configuration validation failed:\n${t}`)}return this.config}}export class ConfigManager{constructor(){this.parser=new CommandLineParser,this.builder=new ServerConfigBuilder}createConfig(e){try{const t=this.parser.parse(e),i=ConfigValidator.validateArgs(t);if(!i.isValid){const e=i.errors.map(e=>`${e.field}: ${e.message}`).join("\n");throw new Error(`Invalid command-line arguments:\n${e}`)}return this.builder.fromCommandLine(t).fromEnvironment(t.environment||"production").withDefaults().build()}catch(e){const t=e instanceof Error?e.message:String(e);throw new Error(`Failed to create configuration: ${t}`)}}showHelp(){this.parser.showHelp()}getEnvironmentConfigs(){return ENVIRONMENT_CONFIGS}getDefaults(){return DEFAULT_CONFIG}}export const configManager=new ConfigManager;