[project] name = "cloudprice-mcp" version = "0.18.3" description = "The FinOps MCP server: multi-cloud cost + carbon across AWS, Azure, GCP, OCI. 25 tools — RI break-even, TCO, migration, egress arbitrage, spot, GPU pricing, anomaly detection, CFO decision reports, carbon (kg CO2e), FOCUS 1.3 export (Vantage / Apptio / Cost Mgmt / OpenCost). LLM token pricing across ~2300 model/provider combinations (Anthropic, OpenAI, Bedrock, Vertex, Azure OpenAI, Together, Fireworks, Groq, OpenRouter). Weekly auto-refresh + price-history snapshots." readme = "README.md" requires-python = ">=3.10" license = { text = "MIT" } authors = [{ name = "Ali Albaker", email = "baker.ali.m@gmail.com" }] keywords = ["finops", "mcp", "cloud", "pricing", "cost", "tco", "aws", "azure", "gcp", "oci", "reserved-instances", "savings-plan", "claude", "copilot", "cursor"] classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: System :: Systems Administration", ] dependencies = [ # mcp 2.x removed Server.list_tools; the stdio server in server.py # targets the 1.x low-level API. Cap until that migration is done. "mcp>=1.0.0,<2", "httpx>=0.27.0", "pyyaml>=6.0", ] [project.optional-dependencies] dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "pytest-httpx>=0.30", "ruff>=0.5", ] [project.scripts] cloudprice-mcp = "cloudprice_mcp.cli:main" [project.urls] Homepage = "https://albaker.info" Repository = "https://github.com/Albaker-Group/cloudprice-mcp" Issues = "https://github.com/Albaker-Group/cloudprice-mcp/issues" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/cloudprice_mcp"] [tool.ruff] line-length = 100 target-version = "py310" [tool.ruff.lint] # CI runs ruff as a gate, so the rule set has to reflect decisions actually # made in this codebase rather than every default preference. Each of these is # ignored deliberately; none is ignored because it was inconvenient. ignore = [ # date.today() is correct here. The price catalogue is dated in local terms # and compared against date-only snapshot filenames; forcing timezone-aware # datetimes would change what "today's prices" means. "DTZ011", # subprocess.run without check= is intentional in the refresh scripts and the # Lambda builder: they inspect returncode themselves and report a useful # message. Adding check=True would replace that with a raw CalledProcessError. "PLW1510", # Catching broad exceptions is deliberate at tool boundaries. A tool returns # {"error": ...} so the model can report what broke; letting it raise gives # the caller an opaque failure instead. "BLE001", # Style preferences that do not change behaviour and are not worth churning # working code over. "ISC004", "PERF102", "SIM102", "PIE810", "UP031", ] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"]