@jadenrazo/cloudcost-mcp 0.1.8 → 0.2.0
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/README.md +12 -8
- package/dist/index.js +131 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
<h1 align="center">CloudCost MCP Server</h1>
|
|
2
|
+
|
|
1
3
|
<p align="center">
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
<br />
|
|
6
|
-
Built on the <a href="https://modelcontextprotocol.io">Model Context Protocol</a> for seamless AI agent integration.
|
|
7
|
-
</p>
|
|
4
|
+
Multi-cloud cost analysis for Terraform — powered by live pricing data from AWS, Azure, and GCP.
|
|
5
|
+
<br />
|
|
6
|
+
Built on the <a href="https://modelcontextprotocol.io">Model Context Protocol</a> for seamless AI agent integration.
|
|
8
7
|
</p>
|
|
9
8
|
|
|
10
9
|
<p align="center">
|
|
10
|
+
<a href="https://github.com/jadenrazo/CloudCostMCP/actions/workflows/ci.yml"><img src="https://github.com/jadenrazo/CloudCostMCP/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jadenrazo/cloudcost-mcp"><img src="https://img.shields.io/npm/v/@jadenrazo/cloudcost-mcp.svg" alt="npm version" /></a>
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="Node.js" />
|
|
14
|
+
</p>
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
<p align="right">
|
|
17
|
+
<img src="https://github.com/user-attachments/assets/7d5f613a-851e-4480-900f-438d13f9a56e" alt="CloudCost MCP demo" width="700" />
|
|
14
18
|
</p>
|
|
15
19
|
|
|
16
20
|
<p align="center">
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-KZJSZMWM.js";
|
|
15
15
|
|
|
16
16
|
// src/server.ts
|
|
17
|
+
import { createRequire } from "module";
|
|
17
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
19
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
20
|
|
|
@@ -55,8 +56,9 @@ function loadEnvConfig() {
|
|
|
55
56
|
const config = {};
|
|
56
57
|
const env = process.env;
|
|
57
58
|
if (env.CLOUDCOST_CACHE_TTL || env.CLOUDCOST_CACHE_PATH) {
|
|
59
|
+
const parsedTtl = env.CLOUDCOST_CACHE_TTL ? parseInt(env.CLOUDCOST_CACHE_TTL, 10) : NaN;
|
|
58
60
|
config.cache = {
|
|
59
|
-
ttl_seconds:
|
|
61
|
+
ttl_seconds: !isNaN(parsedTtl) ? parsedTtl : DEFAULT_CONFIG.cache.ttl_seconds,
|
|
60
62
|
db_path: env.CLOUDCOST_CACHE_PATH ?? ""
|
|
61
63
|
};
|
|
62
64
|
}
|
|
@@ -66,9 +68,10 @@ function loadEnvConfig() {
|
|
|
66
68
|
};
|
|
67
69
|
}
|
|
68
70
|
if (env.CLOUDCOST_MONTHLY_HOURS) {
|
|
71
|
+
const parsedHours = parseInt(env.CLOUDCOST_MONTHLY_HOURS, 10);
|
|
69
72
|
config.pricing = {
|
|
70
73
|
...DEFAULT_CONFIG.pricing,
|
|
71
|
-
monthly_hours:
|
|
74
|
+
monthly_hours: !isNaN(parsedHours) ? parsedHours : DEFAULT_CONFIG.pricing.monthly_hours
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
77
|
return config;
|
|
@@ -157,18 +160,26 @@ var PricingCache = class {
|
|
|
157
160
|
* table self-trims on read.
|
|
158
161
|
*/
|
|
159
162
|
get(key) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
try {
|
|
164
|
+
const row = this.db.prepare(
|
|
165
|
+
"SELECT * FROM pricing_cache WHERE key = ?"
|
|
166
|
+
).get(key);
|
|
167
|
+
if (!row) return null;
|
|
168
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
169
|
+
if (row.expires_at <= now) {
|
|
170
|
+
this.db.prepare("DELETE FROM pricing_cache WHERE key = ?").run(key);
|
|
171
|
+
logger.debug("Cache miss (expired)", { key });
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
logger.debug("Cache hit", { key });
|
|
175
|
+
return JSON.parse(row.data);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.warn("Cache get failed, treating as miss", {
|
|
178
|
+
key,
|
|
179
|
+
err: err instanceof Error ? err.message : String(err)
|
|
180
|
+
});
|
|
168
181
|
return null;
|
|
169
182
|
}
|
|
170
|
-
logger.debug("Cache hit", { key });
|
|
171
|
-
return JSON.parse(row.data);
|
|
172
183
|
}
|
|
173
184
|
/**
|
|
174
185
|
* Store a value in the cache.
|
|
@@ -181,10 +192,11 @@ var PricingCache = class {
|
|
|
181
192
|
* be reused for other cache domains in the future.
|
|
182
193
|
*/
|
|
183
194
|
set(key, data, provider, service, region, ttlSeconds) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
try {
|
|
196
|
+
const now = /* @__PURE__ */ new Date();
|
|
197
|
+
const expiresAt = new Date(now.getTime() + ttlSeconds * 1e3);
|
|
198
|
+
this.db.prepare(
|
|
199
|
+
`INSERT INTO pricing_cache (key, data, provider, service, region, created_at, expires_at)
|
|
188
200
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
189
201
|
ON CONFLICT (key) DO UPDATE SET
|
|
190
202
|
data = excluded.data,
|
|
@@ -193,58 +205,92 @@ var PricingCache = class {
|
|
|
193
205
|
region = excluded.region,
|
|
194
206
|
created_at = excluded.created_at,
|
|
195
207
|
expires_at = excluded.expires_at`
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
).run(
|
|
209
|
+
key,
|
|
210
|
+
JSON.stringify(data),
|
|
211
|
+
provider,
|
|
212
|
+
service,
|
|
213
|
+
region,
|
|
214
|
+
now.toISOString(),
|
|
215
|
+
expiresAt.toISOString()
|
|
216
|
+
);
|
|
217
|
+
logger.debug("Cache set", { key, ttlSeconds });
|
|
218
|
+
} catch (err) {
|
|
219
|
+
logger.warn("Cache set failed, continuing without caching", {
|
|
220
|
+
key,
|
|
221
|
+
err: err instanceof Error ? err.message : String(err)
|
|
222
|
+
});
|
|
223
|
+
}
|
|
206
224
|
}
|
|
207
225
|
/** Remove a single cache entry. */
|
|
208
226
|
invalidate(key) {
|
|
209
|
-
|
|
210
|
-
|
|
227
|
+
try {
|
|
228
|
+
const result = this.db.prepare("DELETE FROM pricing_cache WHERE key = ?").run(key);
|
|
229
|
+
logger.debug("Cache invalidated", { key, removed: result.changes });
|
|
230
|
+
} catch (err) {
|
|
231
|
+
logger.warn("Cache invalidate failed", {
|
|
232
|
+
key,
|
|
233
|
+
err: err instanceof Error ? err.message : String(err)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
211
236
|
}
|
|
212
237
|
/** Remove every cache entry belonging to a provider. */
|
|
213
238
|
invalidateByProvider(provider) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
provider,
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
try {
|
|
240
|
+
const result = this.db.prepare("DELETE FROM pricing_cache WHERE provider = ?").run(provider);
|
|
241
|
+
logger.debug("Cache invalidated by provider", {
|
|
242
|
+
provider,
|
|
243
|
+
removed: result.changes
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
logger.warn("Cache invalidateByProvider failed", {
|
|
247
|
+
provider,
|
|
248
|
+
err: err instanceof Error ? err.message : String(err)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
219
251
|
}
|
|
220
252
|
/**
|
|
221
253
|
* Delete all entries whose expiry timestamp is in the past.
|
|
222
254
|
* Returns the number of rows removed.
|
|
223
255
|
*/
|
|
224
256
|
cleanup() {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
257
|
+
try {
|
|
258
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
259
|
+
const result = this.db.prepare("DELETE FROM pricing_cache WHERE expires_at <= ?").run(now);
|
|
260
|
+
const removed = result.changes;
|
|
261
|
+
logger.debug("Cache cleanup complete", { removed });
|
|
262
|
+
return removed;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
logger.warn("Cache cleanup failed", {
|
|
265
|
+
err: err instanceof Error ? err.message : String(err)
|
|
266
|
+
});
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
230
269
|
}
|
|
231
270
|
/** Return aggregate statistics about the current cache state. */
|
|
232
271
|
getStats() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
272
|
+
try {
|
|
273
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
274
|
+
const totals = this.db.prepare(
|
|
275
|
+
`SELECT
|
|
236
276
|
COUNT(*) AS total_entries,
|
|
237
277
|
COALESCE(SUM(LENGTH(data)), 0) AS size_bytes
|
|
238
278
|
FROM pricing_cache`
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
279
|
+
).get();
|
|
280
|
+
const expired = this.db.prepare(
|
|
281
|
+
"SELECT COUNT(*) AS expired_entries FROM pricing_cache WHERE expires_at <= ?"
|
|
282
|
+
).get(now);
|
|
283
|
+
return {
|
|
284
|
+
total_entries: totals.total_entries,
|
|
285
|
+
expired_entries: expired.expired_entries,
|
|
286
|
+
size_bytes: totals.size_bytes
|
|
287
|
+
};
|
|
288
|
+
} catch (err) {
|
|
289
|
+
logger.warn("Cache getStats failed", {
|
|
290
|
+
err: err instanceof Error ? err.message : String(err)
|
|
291
|
+
});
|
|
292
|
+
return { total_entries: 0, expired_entries: 0, size_bytes: 0 };
|
|
293
|
+
}
|
|
248
294
|
}
|
|
249
295
|
/** Close the underlying database connection. */
|
|
250
296
|
close() {
|
|
@@ -920,7 +966,8 @@ var AwsBulkLoader = class _AwsBulkLoader {
|
|
|
920
966
|
extractEc2Price(bulk, instanceType, region, os) {
|
|
921
967
|
const products = bulk?.products ?? {};
|
|
922
968
|
const onDemand = bulk?.terms?.OnDemand ?? {};
|
|
923
|
-
|
|
969
|
+
const sortedEntries = Object.entries(products).sort(([a], [b]) => a.localeCompare(b));
|
|
970
|
+
for (const [sku, product] of sortedEntries) {
|
|
924
971
|
const attrs = product?.attributes ?? {};
|
|
925
972
|
if (attrs.instanceType === instanceType && (attrs.operatingSystem ?? "").toLowerCase() === os.toLowerCase() && attrs.tenancy === "Shared" && attrs.capacitystatus === "Used") {
|
|
926
973
|
const priceTerms = onDemand[sku];
|
|
@@ -934,7 +981,8 @@ var AwsBulkLoader = class _AwsBulkLoader {
|
|
|
934
981
|
extractRdsPrice(bulk, instanceClass, region, engine) {
|
|
935
982
|
const products = bulk?.products ?? {};
|
|
936
983
|
const onDemand = bulk?.terms?.OnDemand ?? {};
|
|
937
|
-
|
|
984
|
+
const sortedEntries = Object.entries(products).sort(([a], [b]) => a.localeCompare(b));
|
|
985
|
+
for (const [sku, product] of sortedEntries) {
|
|
938
986
|
const attrs = product?.attributes ?? {};
|
|
939
987
|
if (attrs.instanceType === instanceClass && (attrs.databaseEngine ?? "").toLowerCase().includes(engine.toLowerCase()) && attrs.deploymentOption === "Single-AZ") {
|
|
940
988
|
const priceTerms = onDemand[sku];
|
|
@@ -948,7 +996,8 @@ var AwsBulkLoader = class _AwsBulkLoader {
|
|
|
948
996
|
extractEbsPrice(bulk, volumeType, region) {
|
|
949
997
|
const products = bulk?.products ?? {};
|
|
950
998
|
const onDemand = bulk?.terms?.OnDemand ?? {};
|
|
951
|
-
|
|
999
|
+
const sortedEntries = Object.entries(products).sort(([a], [b]) => a.localeCompare(b));
|
|
1000
|
+
for (const [sku, product] of sortedEntries) {
|
|
952
1001
|
const attrs = product?.attributes ?? {};
|
|
953
1002
|
const apiName = (attrs.volumeApiName ?? "").toLowerCase();
|
|
954
1003
|
if (product?.productFamily === "Storage" && (apiName === volumeType.toLowerCase() || (attrs.volumeType ?? "").toLowerCase() === volumeType.toLowerCase())) {
|
|
@@ -1124,7 +1173,7 @@ function normalizeAzureCompute(item) {
|
|
|
1124
1173
|
service_name: item.serviceName ?? "Virtual Machines",
|
|
1125
1174
|
product_name: item.productName ?? "",
|
|
1126
1175
|
meter_name: item.meterName ?? "",
|
|
1127
|
-
pricing_source: "
|
|
1176
|
+
pricing_source: "live"
|
|
1128
1177
|
},
|
|
1129
1178
|
effective_date: item.effectiveStartDate ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1130
1179
|
};
|
|
@@ -1144,7 +1193,7 @@ function normalizeAzureDatabase(item) {
|
|
|
1144
1193
|
service_name: item.serviceName ?? "",
|
|
1145
1194
|
product_name: item.productName ?? "",
|
|
1146
1195
|
meter_name: item.meterName ?? "",
|
|
1147
|
-
pricing_source: "
|
|
1196
|
+
pricing_source: "live"
|
|
1148
1197
|
},
|
|
1149
1198
|
effective_date: item.effectiveStartDate ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1150
1199
|
};
|
|
@@ -1164,7 +1213,7 @@ function normalizeAzureStorage(item) {
|
|
|
1164
1213
|
service_name: item.serviceName ?? "",
|
|
1165
1214
|
product_name: item.productName ?? "",
|
|
1166
1215
|
meter_name: item.meterName ?? "",
|
|
1167
|
-
pricing_source: "
|
|
1216
|
+
pricing_source: "live"
|
|
1168
1217
|
},
|
|
1169
1218
|
effective_date: item.effectiveStartDate ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1170
1219
|
};
|
|
@@ -1348,6 +1397,7 @@ var AzureRetailClient = class {
|
|
|
1348
1397
|
const exactFilter = `serviceName eq '${serviceName}' and armRegionName eq '${armRegion}' and priceType eq 'Consumption' and armSkuName eq '${vmName}'`;
|
|
1349
1398
|
const exactItems = await this.queryPricing(exactFilter);
|
|
1350
1399
|
if (exactItems.length > 0) {
|
|
1400
|
+
exactItems.sort((a, b) => (a.skuId ?? "").localeCompare(b.skuId ?? ""));
|
|
1351
1401
|
const match = exactItems.find(
|
|
1352
1402
|
(i) => (i.meterName ?? "").toLowerCase().includes("vcore")
|
|
1353
1403
|
) ?? exactItems[0];
|
|
@@ -1360,6 +1410,7 @@ var AzureRetailClient = class {
|
|
|
1360
1410
|
const seriesFilter = `serviceName eq '${serviceName}' and armRegionName eq '${armRegion}' and priceType eq 'Consumption' and contains(armSkuName, '${seriesFamily}') and contains(armSkuName, '${parsed.series}')`;
|
|
1361
1411
|
const seriesItems = await this.queryPricing(seriesFilter);
|
|
1362
1412
|
if (seriesItems.length > 0) {
|
|
1413
|
+
seriesItems.sort((a, b) => (a.skuId ?? "").localeCompare(b.skuId ?? ""));
|
|
1363
1414
|
const perVcore = seriesItems[0].retailPrice ?? 0;
|
|
1364
1415
|
const totalPrice = perVcore * parsed.vcpus;
|
|
1365
1416
|
const adjusted = { ...seriesItems[0], retailPrice: totalPrice };
|
|
@@ -1376,10 +1427,10 @@ var AzureRetailClient = class {
|
|
|
1376
1427
|
/Standard_([A-Z])(\d+)([a-z]*)_v(\d+)/i
|
|
1377
1428
|
);
|
|
1378
1429
|
if (!match) return null;
|
|
1379
|
-
const [, family, vcpuStr, variant,
|
|
1430
|
+
const [, family, vcpuStr, variant, version2] = match;
|
|
1380
1431
|
const vcpus = parseInt(vcpuStr, 10);
|
|
1381
1432
|
if (isNaN(vcpus) || vcpus === 0) return null;
|
|
1382
|
-
const series = `${family}${variant}v${
|
|
1433
|
+
const series = `${family}${variant}v${version2}`;
|
|
1383
1434
|
return { series, vcpus };
|
|
1384
1435
|
}
|
|
1385
1436
|
async getStoragePrice(diskType, region) {
|
|
@@ -1391,6 +1442,7 @@ var AzureRetailClient = class {
|
|
|
1391
1442
|
const filter = this.buildODataFilter("Storage", armRegion, diskType);
|
|
1392
1443
|
const items = await this.queryPricing(filter);
|
|
1393
1444
|
if (items.length > 0) {
|
|
1445
|
+
items.sort((a, b) => (a.skuId ?? "").localeCompare(b.skuId ?? ""));
|
|
1394
1446
|
const result = normalizeAzureStorage(items[0]);
|
|
1395
1447
|
this.cache.set(cacheKey, result, "azure", "storage", region, CACHE_TTL2);
|
|
1396
1448
|
return result;
|
|
@@ -1501,16 +1553,19 @@ var AzureRetailClient = class {
|
|
|
1501
1553
|
*/
|
|
1502
1554
|
pickVmItem(items, vmSize, os) {
|
|
1503
1555
|
if (items.length === 0) return null;
|
|
1556
|
+
const sorted = [...items].sort(
|
|
1557
|
+
(a, b) => (a.skuId ?? "").localeCompare(b.skuId ?? "")
|
|
1558
|
+
);
|
|
1504
1559
|
const vmLower = vmSize.toLowerCase();
|
|
1505
1560
|
const isWindows = os.toLowerCase().includes("windows");
|
|
1506
|
-
for (const item of
|
|
1561
|
+
for (const item of sorted) {
|
|
1507
1562
|
const skuLower = (item.skuName ?? "").toLowerCase();
|
|
1508
1563
|
const hasWindows = skuLower.includes("windows");
|
|
1509
1564
|
if (skuLower.includes(vmLower) && (isWindows && hasWindows || !isWindows && !hasWindows)) {
|
|
1510
1565
|
return item;
|
|
1511
1566
|
}
|
|
1512
1567
|
}
|
|
1513
|
-
return
|
|
1568
|
+
return sorted[0];
|
|
1514
1569
|
}
|
|
1515
1570
|
// -------------------------------------------------------------------------
|
|
1516
1571
|
// Internal helpers – size interpolation
|
|
@@ -1995,8 +2050,11 @@ var GcpProvider = class {
|
|
|
1995
2050
|
getNatGatewayPrice(region) {
|
|
1996
2051
|
return this.loader.getNatGatewayPrice(region);
|
|
1997
2052
|
}
|
|
1998
|
-
getKubernetesPrice(region) {
|
|
1999
|
-
return this.loader.getKubernetesPrice(
|
|
2053
|
+
getKubernetesPrice(region, mode) {
|
|
2054
|
+
return this.loader.getKubernetesPrice(
|
|
2055
|
+
region,
|
|
2056
|
+
mode === "autopilot" ? "autopilot" : "standard"
|
|
2057
|
+
);
|
|
2000
2058
|
}
|
|
2001
2059
|
};
|
|
2002
2060
|
var PricingEngine = class {
|
|
@@ -2056,7 +2114,7 @@ var PricingEngine = class {
|
|
|
2056
2114
|
return p.getNatGatewayPrice(region);
|
|
2057
2115
|
}
|
|
2058
2116
|
if (svc === "k8s" || svc === "kubernetes" || svc === "eks" || svc === "aks" || svc === "gke") {
|
|
2059
|
-
return p.getKubernetesPrice(region);
|
|
2117
|
+
return p.getKubernetesPrice(region, attributes.mode);
|
|
2060
2118
|
}
|
|
2061
2119
|
logger.warn("PricingEngine: unrecognised service", { service, provider });
|
|
2062
2120
|
return null;
|
|
@@ -2979,7 +3037,7 @@ function findNearestInstance(spec, targetProvider) {
|
|
|
2979
3037
|
if (candidate.category === spec.category) {
|
|
2980
3038
|
score += SCORE_CATEGORY;
|
|
2981
3039
|
}
|
|
2982
|
-
if (score > bestScore) {
|
|
3040
|
+
if (score > bestScore || score === bestScore && bestType !== null && candidate.instance_type < bestType) {
|
|
2983
3041
|
bestScore = score;
|
|
2984
3042
|
bestType = candidate.instance_type;
|
|
2985
3043
|
}
|
|
@@ -3716,7 +3774,9 @@ var CostEngine = class {
|
|
|
3716
3774
|
total_monthly: Math.round(totalMonthly * 100) / 100,
|
|
3717
3775
|
total_yearly: Math.round(totalMonthly * 12 * 100) / 100,
|
|
3718
3776
|
currency: "USD",
|
|
3719
|
-
by_service:
|
|
3777
|
+
by_service: Object.fromEntries(
|
|
3778
|
+
Object.entries(byService).map(([k, v]) => [k, Math.round(v * 100) / 100])
|
|
3779
|
+
),
|
|
3720
3780
|
by_resource: estimates,
|
|
3721
3781
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3722
3782
|
warnings
|
|
@@ -4363,7 +4423,7 @@ async function getEquivalents(params) {
|
|
|
4363
4423
|
import { z as z5 } from "zod";
|
|
4364
4424
|
var getPricingSchema = z5.object({
|
|
4365
4425
|
provider: z5.enum(["aws", "azure", "gcp"]).describe("Cloud provider to look up pricing for"),
|
|
4366
|
-
service: z5.enum(["compute", "database", "storage", "network", "kubernetes"]).describe("Service category"),
|
|
4426
|
+
service: z5.enum(["compute", "database", "storage", "network", "load_balancer", "nat_gateway", "kubernetes"]).describe("Service category (network defaults to nat_gateway for backward compatibility)"),
|
|
4367
4427
|
resource_type: z5.string().describe(
|
|
4368
4428
|
"Instance type, storage type, or resource identifier (e.g. t3.large, gp3, Standard_D4s_v3)"
|
|
4369
4429
|
),
|
|
@@ -4376,6 +4436,8 @@ async function getPricing(params, pricingEngine) {
|
|
|
4376
4436
|
database: "database",
|
|
4377
4437
|
storage: "storage",
|
|
4378
4438
|
network: "nat-gateway",
|
|
4439
|
+
load_balancer: "load-balancer",
|
|
4440
|
+
nat_gateway: "nat-gateway",
|
|
4379
4441
|
kubernetes: "kubernetes"
|
|
4380
4442
|
};
|
|
4381
4443
|
const service = serviceMap[params.service] ?? params.service;
|
|
@@ -4514,12 +4576,14 @@ function registerTools(server, config) {
|
|
|
4514
4576
|
}
|
|
4515
4577
|
|
|
4516
4578
|
// src/server.ts
|
|
4579
|
+
var require2 = createRequire(import.meta.url);
|
|
4580
|
+
var { version } = require2("../package.json");
|
|
4517
4581
|
async function startServer() {
|
|
4518
4582
|
const config = loadConfig();
|
|
4519
4583
|
setLogLevel(config.logging.level);
|
|
4520
4584
|
const server = new McpServer({
|
|
4521
4585
|
name: "cloudcost-mcp",
|
|
4522
|
-
version
|
|
4586
|
+
version
|
|
4523
4587
|
});
|
|
4524
4588
|
registerTools(server, config);
|
|
4525
4589
|
const transport = new StdioServerTransport();
|