@jadenrazo/cloudcost-mcp 0.5.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/MIGRATION.md +40 -0
- package/STABILITY.md +71 -0
- package/data/aws-pricing/metadata.json +8 -0
- package/data/azure-pricing/metadata.json +8 -0
- package/data/gcp-pricing/metadata.json +4 -2
- package/dist/{chunk-E7KOWAMW.js → chunk-6O2Y6MKU.js} +1139 -198
- package/dist/{chunk-TRRAOOVF.js → chunk-MNFT5YKN.js} +13 -1
- package/dist/cli.js +2 -2
- package/dist/index.js +43 -21
- package/dist/{loader-VXYJYDIH.js → loader-UWVEXYMR.js} +4 -2
- package/package.json +12 -3
|
@@ -121,6 +121,17 @@ function getRegionPriceMultipliers() {
|
|
|
121
121
|
}
|
|
122
122
|
return _regionPriceMultipliers;
|
|
123
123
|
}
|
|
124
|
+
var _fallbackMeta = {};
|
|
125
|
+
function getFallbackMetadata(provider) {
|
|
126
|
+
if (_fallbackMeta[provider] !== void 0) return _fallbackMeta[provider];
|
|
127
|
+
const relPath = `${provider}-pricing/metadata.json`;
|
|
128
|
+
try {
|
|
129
|
+
_fallbackMeta[provider] = loadJsonFile(relPath);
|
|
130
|
+
} catch {
|
|
131
|
+
_fallbackMeta[provider] = null;
|
|
132
|
+
}
|
|
133
|
+
return _fallbackMeta[provider];
|
|
134
|
+
}
|
|
124
135
|
function _resetLoaderCache() {
|
|
125
136
|
_resourceEquivalents = null;
|
|
126
137
|
_instanceMap = null;
|
|
@@ -149,6 +160,7 @@ export {
|
|
|
149
160
|
getGcpStoragePricing,
|
|
150
161
|
getGcpDiskPricing,
|
|
151
162
|
getRegionPriceMultipliers,
|
|
163
|
+
getFallbackMetadata,
|
|
152
164
|
_resetLoaderCache
|
|
153
165
|
};
|
|
154
|
-
//# sourceMappingURL=chunk-
|
|
166
|
+
//# sourceMappingURL=chunk-MNFT5YKN.js.map
|
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
optimizeCost,
|
|
11
11
|
whatIf
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-6O2Y6MKU.js";
|
|
13
|
+
import "./chunk-MNFT5YKN.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
import { readFileSync, existsSync, statSync } from "fs";
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
convertCurrency,
|
|
14
14
|
estimateCost,
|
|
15
15
|
estimateCostSchema,
|
|
16
|
+
fileContentSchema,
|
|
17
|
+
filePathSchema,
|
|
16
18
|
loadConfig,
|
|
17
19
|
logger,
|
|
18
20
|
mapInstance,
|
|
@@ -21,14 +23,20 @@ import {
|
|
|
21
23
|
optimizeCost,
|
|
22
24
|
optimizeCostSchema,
|
|
23
25
|
parseTerraform,
|
|
26
|
+
planJsonSchema,
|
|
27
|
+
safeJsonParse,
|
|
28
|
+
sanitizeForMessage,
|
|
24
29
|
setLogLevel,
|
|
30
|
+
stateJsonSchema,
|
|
25
31
|
str,
|
|
32
|
+
tfvarsSchema,
|
|
26
33
|
whatIf,
|
|
27
34
|
whatIfSchema
|
|
28
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6O2Y6MKU.js";
|
|
29
36
|
import {
|
|
37
|
+
getFallbackMetadata,
|
|
30
38
|
getResourceEquivalents
|
|
31
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-MNFT5YKN.js";
|
|
32
40
|
|
|
33
41
|
// src/server.ts
|
|
34
42
|
import { createRequire } from "module";
|
|
@@ -120,8 +128,11 @@ var getPricingSchema = z2.object({
|
|
|
120
128
|
"network",
|
|
121
129
|
"load_balancer",
|
|
122
130
|
"nat_gateway",
|
|
123
|
-
"kubernetes"
|
|
124
|
-
|
|
131
|
+
"kubernetes",
|
|
132
|
+
"gpu"
|
|
133
|
+
]).describe(
|
|
134
|
+
"Service category (network defaults to nat_gateway for backward compatibility; gpu returns accelerator pricing where supported)"
|
|
135
|
+
),
|
|
125
136
|
resource_type: z2.string().describe(
|
|
126
137
|
"Instance type, storage type, or resource identifier (e.g. t3.large, gp3, Standard_D4s_v3)"
|
|
127
138
|
),
|
|
@@ -136,7 +147,8 @@ async function getPricing(params, pricingEngine) {
|
|
|
136
147
|
network: "nat-gateway",
|
|
137
148
|
load_balancer: "load-balancer",
|
|
138
149
|
nat_gateway: "nat-gateway",
|
|
139
|
-
kubernetes: "kubernetes"
|
|
150
|
+
kubernetes: "kubernetes",
|
|
151
|
+
gpu: "gpu"
|
|
140
152
|
};
|
|
141
153
|
const service = serviceMap[params.service] ?? params.service;
|
|
142
154
|
const rawPrice = await pricingEngine.getPrice(
|
|
@@ -157,7 +169,14 @@ async function getPricing(params, pricingEngine) {
|
|
|
157
169
|
price.attributes = attributes;
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
|
-
|
|
172
|
+
const meta = getFallbackMetadata(provider);
|
|
173
|
+
const fallback_metadata = meta ? {
|
|
174
|
+
last_updated: meta.last_updated,
|
|
175
|
+
source: meta.source,
|
|
176
|
+
sku_count: meta.sku_count,
|
|
177
|
+
refresh_script_version: meta.refresh_script_version
|
|
178
|
+
} : void 0;
|
|
179
|
+
return fallback_metadata ? { price, fallback_metadata } : { price };
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
// src/tools/analyze-plan.ts
|
|
@@ -307,10 +326,10 @@ function buildInventory(resources, warnings) {
|
|
|
307
326
|
function parseTerraformPlan(planJson) {
|
|
308
327
|
let plan;
|
|
309
328
|
try {
|
|
310
|
-
plan =
|
|
329
|
+
plan = safeJsonParse(planJson);
|
|
311
330
|
} catch (err) {
|
|
312
331
|
const msg = err instanceof Error ? err.message : String(err);
|
|
313
|
-
throw new Error(`Invalid plan JSON: ${msg}`);
|
|
332
|
+
throw new Error(`Invalid plan JSON: ${sanitizeForMessage(msg, 512)}`);
|
|
314
333
|
}
|
|
315
334
|
if (!Array.isArray(plan.resource_changes)) {
|
|
316
335
|
throw new Error("Invalid plan JSON: missing or non-array resource_changes field");
|
|
@@ -375,7 +394,9 @@ function parseTerraformPlan(planJson) {
|
|
|
375
394
|
|
|
376
395
|
// src/tools/analyze-plan.ts
|
|
377
396
|
var analyzePlanSchema = z3.object({
|
|
378
|
-
plan_json:
|
|
397
|
+
plan_json: planJsonSchema.describe(
|
|
398
|
+
"Output of 'terraform show -json <planfile>' or 'terraform plan -json'"
|
|
399
|
+
),
|
|
379
400
|
provider: z3.enum(["aws", "azure", "gcp"]).optional().describe("Target provider for cost estimation. If omitted, auto-detected from plan."),
|
|
380
401
|
region: z3.string().optional().describe("Target region. If omitted, detected from plan resources."),
|
|
381
402
|
currency: z3.enum(SUPPORTED_CURRENCIES).optional().default("USD").describe("Output currency")
|
|
@@ -567,17 +588,18 @@ function parseTerraformState(stateJson) {
|
|
|
567
588
|
const warnings = [];
|
|
568
589
|
let state;
|
|
569
590
|
try {
|
|
570
|
-
state =
|
|
591
|
+
state = safeJsonParse(stateJson);
|
|
571
592
|
} catch (err) {
|
|
572
593
|
const msg = err instanceof Error ? err.message : String(err);
|
|
573
|
-
|
|
594
|
+
const safeMsg = sanitizeForMessage(msg, 512);
|
|
595
|
+
logger.error("Failed to parse Terraform state JSON", { error: safeMsg });
|
|
574
596
|
return {
|
|
575
597
|
provider: "aws",
|
|
576
598
|
region: PROVIDER_DEFAULTS.aws,
|
|
577
599
|
resources: [],
|
|
578
600
|
total_count: 0,
|
|
579
601
|
by_type: {},
|
|
580
|
-
parse_warnings: [`Invalid state JSON: ${
|
|
602
|
+
parse_warnings: [`Invalid state JSON: ${safeMsg}`]
|
|
581
603
|
};
|
|
582
604
|
}
|
|
583
605
|
if (!Array.isArray(state.resources)) {
|
|
@@ -657,14 +679,14 @@ function parseTerraformState(stateJson) {
|
|
|
657
679
|
|
|
658
680
|
// src/tools/compare-actual.ts
|
|
659
681
|
var compareActualSchema = z4.object({
|
|
660
|
-
state_json:
|
|
682
|
+
state_json: stateJsonSchema.describe("Contents of terraform.tfstate file (JSON)"),
|
|
661
683
|
files: z4.array(
|
|
662
684
|
z4.object({
|
|
663
|
-
path:
|
|
664
|
-
content:
|
|
685
|
+
path: filePathSchema.describe("File path"),
|
|
686
|
+
content: fileContentSchema.describe("File content (HCL)")
|
|
665
687
|
})
|
|
666
|
-
).optional().describe("Optional Terraform files to compare planned costs against actual"),
|
|
667
|
-
tfvars:
|
|
688
|
+
).max(2e3, "files array exceeds 2000 entries").optional().describe("Optional Terraform files to compare planned costs against actual"),
|
|
689
|
+
tfvars: tfvarsSchema.optional().describe("Contents of terraform.tfvars file"),
|
|
668
690
|
provider: z4.enum(["aws", "azure", "gcp"]).optional().describe("Target cloud provider override. Defaults to auto-detecting from the state."),
|
|
669
691
|
region: z4.string().optional().describe(
|
|
670
692
|
"Target region override for pricing. Defaults to the region detected from the state."
|
|
@@ -787,11 +809,11 @@ import { z as z6 } from "zod";
|
|
|
787
809
|
var detectAnomaliesSchema = z6.object({
|
|
788
810
|
files: z6.array(
|
|
789
811
|
z6.object({
|
|
790
|
-
path:
|
|
791
|
-
content:
|
|
812
|
+
path: filePathSchema.describe("File path"),
|
|
813
|
+
content: fileContentSchema.describe("File content")
|
|
792
814
|
})
|
|
793
|
-
),
|
|
794
|
-
tfvars:
|
|
815
|
+
).max(2e3, "files array exceeds 2000 entries"),
|
|
816
|
+
tfvars: tfvarsSchema.optional().describe("Variable overrides"),
|
|
795
817
|
provider: z6.enum(["aws", "azure", "gcp"]).describe("Target cloud provider"),
|
|
796
818
|
region: z6.string().optional().describe("Target region"),
|
|
797
819
|
currency: z6.enum(SUPPORTED_CURRENCIES).optional().default("USD").describe("Output currency for cost estimates. Defaults to USD."),
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
_resetLoaderCache,
|
|
4
4
|
getAwsInstances,
|
|
5
5
|
getAzureVmSizes,
|
|
6
|
+
getFallbackMetadata,
|
|
6
7
|
getGcpComputePricing,
|
|
7
8
|
getGcpDiskPricing,
|
|
8
9
|
getGcpMachineTypes,
|
|
@@ -13,11 +14,12 @@ import {
|
|
|
13
14
|
getRegionPriceMultipliers,
|
|
14
15
|
getResourceEquivalents,
|
|
15
16
|
getStorageMap
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-MNFT5YKN.js";
|
|
17
18
|
export {
|
|
18
19
|
_resetLoaderCache,
|
|
19
20
|
getAwsInstances,
|
|
20
21
|
getAzureVmSizes,
|
|
22
|
+
getFallbackMetadata,
|
|
21
23
|
getGcpComputePricing,
|
|
22
24
|
getGcpDiskPricing,
|
|
23
25
|
getGcpMachineTypes,
|
|
@@ -29,4 +31,4 @@ export {
|
|
|
29
31
|
getResourceEquivalents,
|
|
30
32
|
getStorageMap
|
|
31
33
|
};
|
|
32
|
-
//# sourceMappingURL=loader-
|
|
34
|
+
//# sourceMappingURL=loader-UWVEXYMR.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jadenrazo/cloudcost-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server for multi-cloud cost analysis of Terraform, CloudFormation, Pulumi, and Bicep/ARM codebases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
"!dist/**/*.map",
|
|
40
40
|
"data",
|
|
41
41
|
"README.md",
|
|
42
|
-
"LICENSE"
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"STABILITY.md",
|
|
44
|
+
"MIGRATION.md",
|
|
45
|
+
"CHANGELOG.md"
|
|
43
46
|
],
|
|
44
47
|
"main": "dist/index.js",
|
|
45
48
|
"types": "dist/index.d.ts",
|
|
@@ -66,11 +69,17 @@
|
|
|
66
69
|
},
|
|
67
70
|
"dependencies": {
|
|
68
71
|
"@cdktf/hcl2json": "^0.21.0",
|
|
69
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
72
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
70
73
|
"better-sqlite3": "^11.9.1",
|
|
71
74
|
"yaml": "^2.8.3",
|
|
72
75
|
"zod": "^3.24.4"
|
|
73
76
|
},
|
|
77
|
+
"overrides": {
|
|
78
|
+
"hono": "^4.12.12",
|
|
79
|
+
"@hono/node-server": "^1.19.13",
|
|
80
|
+
"path-to-regexp": "^8.4.0",
|
|
81
|
+
"vite": "^7.3.2"
|
|
82
|
+
},
|
|
74
83
|
"devDependencies": {
|
|
75
84
|
"@eslint/js": "^10.0.1",
|
|
76
85
|
"@types/better-sqlite3": "^7.6.13",
|