sample-aws-pricing-calculator-mcp 1.2.8 → 1.3.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 +53 -32
- package/catalog/schema.json +1 -1
- package/catalog/services/ec2Enhancement.json +22 -11
- package/dist/mcp-server.js +64 -57
- package/mcp-server.js +15 -15
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -84,25 +84,30 @@ Then point your client at the built bundle:
|
|
|
84
84
|
```
|
|
85
85
|
mcp-server.js # Entry point — registers the 9 MCP tools, stdio + HTTP transports
|
|
86
86
|
lib/
|
|
87
|
-
aws
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
trace
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
aws/ # AWS API access + payload construction
|
|
88
|
+
aws-client.js # Manifest loading, service definitions, save/read APIs, selector aggregations
|
|
89
|
+
estimate-builder.js # In-memory estimate model, AWS payload assembly, columnFormIPM remap
|
|
90
|
+
ec2.js # EC2 agent-friendly → ec2Enhancement transform
|
|
91
|
+
pct-config.js # PCT-based field suggestion
|
|
92
|
+
surfaceability.js # PCT-driven surfaceability index
|
|
93
|
+
agent-fields.js # Synthetic field surfacing for fields hidden in composite widgets
|
|
94
|
+
lint/ # Rehydration prediction + pre-save validation
|
|
95
|
+
can-rehydrate.js # Static rehydration linter (pure predicates)
|
|
96
|
+
can-rehydrate-fetch.js # Network wrapper around the linter
|
|
97
|
+
validation.js # Pre-save config validation (field-id, value-shape, region, auto-correct)
|
|
98
|
+
lint-hints.js # Translates lint failures into agent-actionable next_step text
|
|
99
|
+
catalog.js # Loader for catalog/services/*.json curated entries
|
|
100
|
+
mcp/ # MCP tool plumbing
|
|
101
|
+
handler-helpers.js # Shared internals for the MCP tool handlers
|
|
102
|
+
tool-descriptions.js # Long agent-facing tool descriptions (separated from wiring)
|
|
103
|
+
trace/ # Observability
|
|
104
|
+
trace-logger.js # Structured JSON trace events on stderr
|
|
105
|
+
trace-events.js # Trace event name registry
|
|
106
|
+
request-context.js # Per-request session id propagation
|
|
107
|
+
store/ # In-flight estimate persistence
|
|
108
|
+
estimate-store.js # Pluggable store (memory default)
|
|
109
|
+
estimate-store-dynamodb.js # DynamoDB-backed store for stateless multi-replica deployments
|
|
110
|
+
dom-cost.js # Playwright DOM scrape of the calculator's rendered cost (eval oracle)
|
|
106
111
|
catalog/
|
|
107
112
|
schema.json # JSON Schema for catalog entries
|
|
108
113
|
services/ # Verified configs catalog (minimalConfig, traps, subServices)
|
|
@@ -112,7 +117,7 @@ scripts/ # Maintainer tools (catalog authoring, sweep, diagnos
|
|
|
112
117
|
dist/ # Build output: mcp-server.js bundle, aws-calculator.zip, bundle-contract.json
|
|
113
118
|
```
|
|
114
119
|
|
|
115
|
-
The `lib/`
|
|
120
|
+
The `lib/` tree groups modules by layer (aws, lint, mcp, trace, store). The architecture diagram below highlights the load-bearing modules; minor helpers are not shown.
|
|
116
121
|
|
|
117
122
|
## Build
|
|
118
123
|
|
|
@@ -127,13 +132,29 @@ Produces:
|
|
|
127
132
|
|
|
128
133
|
## Tests
|
|
129
134
|
|
|
130
|
-
|
|
135
|
+
Four layers, no overlap:
|
|
131
136
|
|
|
132
|
-
- **`npm test`** — node:test suite
|
|
137
|
+
- **`npm test`** — node:test suite, **hermetic**: 491 pass / 10 skipped, no network, ~1.5s. Per-commit gate, and the only layer that runs in CI. Covers pure functions (validation, lint, surfaceability), payload construction, EC2 transforms, catalog schema.
|
|
138
|
+
- **`npm run test:network`** — the same suite with `SKIP_NETWORK` unset, so the 10 skipped tests run. They fetch the live manifest and POST real estimates to the save API, which is why they are not in CI. Run before a release.
|
|
133
139
|
- **`npm run validate-catalog:cost`** — sweeps verified catalog entries against the live cost oracle. Catches stale URLs and pricing-engine drift.
|
|
134
|
-
- **`python eval/run.py`** — 87 YAML scenarios (scripted MCP calls + LLM-driven). Each does a real save, then asserts on the saved blob (DOM-rendered cost, structural field equality, lint-must-pass).
|
|
140
|
+
- **`python eval/run.py`** — 87 YAML scenarios (scripted MCP calls + LLM-driven). Each does a real save, then asserts on the saved blob (DOM-rendered cost, structural field equality, lint-must-pass). Read `eval/README.md` on interpreting failures — a raw count is not meaningful; use `--compare-baseline`.
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
`npm test` sets `SKIP_NETWORK=1` itself, so a clean checkout is green offline. The
|
|
143
|
+
network-dependent tests declare the gate at their `describe`, following the
|
|
144
|
+
convention in `test/author-catalog.test.js`.
|
|
145
|
+
|
|
146
|
+
### CI
|
|
147
|
+
|
|
148
|
+
`.github/workflows/ci.yml` runs two jobs on every push to `main` and every PR:
|
|
149
|
+
|
|
150
|
+
- **test** — `npm ci && npm test` on Node 22 and 24.
|
|
151
|
+
- **dist-freshness** — rebuilds the bundle and requires `dist/mcp-server.js` and
|
|
152
|
+
`dist/bundle-contract.json` to be byte-identical to what is committed. `dist/`
|
|
153
|
+
is a tracked artifact that consumers install (see `files` in `package.json`),
|
|
154
|
+
so a source change with a stale bundle ships nothing. Expect this job to fail
|
|
155
|
+
on a dependency bump until you rerun `npm run build` — deps are bundled, so
|
|
156
|
+
that is a true positive. `dist/aws-calculator.zip` is not compared, since zip
|
|
157
|
+
embeds mtimes.
|
|
137
158
|
|
|
138
159
|
## Architecture
|
|
139
160
|
|
|
@@ -142,12 +163,12 @@ Three layers, no overlap:
|
|
|
142
163
|
│ MCP Client │◄───────────────────►│ MCP Server │
|
|
143
164
|
│ (Kiro, Claude, │ JSON-RPC over │ │
|
|
144
165
|
│ Cursor, etc.) │ stdin/stdout │ mcp-server.js (entry point) │
|
|
145
|
-
│ │ — or, with │ ├── lib/aws
|
|
146
|
-
│ │ MCP_TRANSPORT │ ├── lib/
|
|
147
|
-
│ │ =http, JSON-RPC │
|
|
148
|
-
│ │ over HTTPS │ ├── lib/
|
|
149
|
-
└─────────────────┘ │ ├── lib/
|
|
150
|
-
│ └── lib/
|
|
166
|
+
│ │ — or, with │ ├── lib/aws/ (client, builder) │
|
|
167
|
+
│ │ MCP_TRANSPORT │ ├── lib/lint/ (can-rehydrate, │
|
|
168
|
+
│ │ =http, JSON-RPC │ │ validation,catalog)│
|
|
169
|
+
│ │ over HTTPS │ ├── lib/mcp/ (handler-helpers) │
|
|
170
|
+
└─────────────────┘ │ ├── lib/trace/ (trace-logger) │
|
|
171
|
+
│ └── lib/store/ (estimate-store) │
|
|
151
172
|
└──────┬─────────┬─────────┬──────────┘
|
|
152
173
|
│ │ │
|
|
153
174
|
GET │ GET │ POST │
|
|
@@ -178,7 +199,7 @@ On first use, the server fetches the AWS Calculator manifest from CloudFront (~4
|
|
|
178
199
|
|
|
179
200
|
### EC2 handling
|
|
180
201
|
|
|
181
|
-
EC2 uses a custom config transform (`lib/ec2.js`) that converts agent-friendly fields (instance type, OS, pricing strategy) into the `ec2Enhancement` format the calculator expects. Supports On-Demand, Savings Plans, Reserved Instances, and Spot pricing.
|
|
202
|
+
EC2 uses a custom config transform (`lib/aws/ec2.js`) that converts agent-friendly fields (instance type, OS, pricing strategy) into the `ec2Enhancement` format the calculator expects. Supports On-Demand, Savings Plans, Reserved Instances, and Spot pricing.
|
|
182
203
|
|
|
183
204
|
### Partition support
|
|
184
205
|
|
|
@@ -383,7 +404,7 @@ Trace events are typically captured to log groups that may be queryable by anyon
|
|
|
383
404
|
|
|
384
405
|
Both fields are replaced with `[redacted: <N> chars]` in `tool.call` args and `tool.result` resultText. Structure is preserved (you still see "the agent set a description") but the content isn't. `resultLength` reflects the original pre-redaction length. Non-sensitive fields — service codes, region IDs, field IDs, numeric values, UUIDs — pass through untouched.
|
|
385
406
|
|
|
386
|
-
To extend redaction for a new sensitive field, add the key to `SENSITIVE_KEYS` in `lib/trace-logger.js`. The redactor walks nested structures recursively, including the JSON-stringified `services` arg of `add_service` / `build_estimate`.
|
|
407
|
+
To extend redaction for a new sensitive field, add the key to `SENSITIVE_KEYS` in `lib/trace/trace-logger.js`. The redactor walks nested structures recursively, including the JSON-stringified `services` arg of `add_service` / `build_estimate`.
|
|
387
408
|
|
|
388
409
|
The `mcpSessionId` field has three states, useful for telling transports apart:
|
|
389
410
|
|
package/catalog/schema.json
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"defaultFields": {
|
|
55
55
|
"type": "object",
|
|
56
56
|
"additionalProperties": true,
|
|
57
|
-
"description": "Per-field literal saved-blob shapes injected at add_service time when the agent omits the key. Use for fields the calculator silently demands but the manifest does NOT mark validations.required (the case where get_service_fields doesn't expose them and the lint can't refuse without them). Authors declare the canonical envelope; lib/handler-helpers.js#applyDefaultFields merges any missing keys into the user's config before estimate.addService."
|
|
57
|
+
"description": "Per-field literal saved-blob shapes injected at add_service time when the agent omits the key. Use for fields the calculator silently demands but the manifest does NOT mark validations.required (the case where get_service_fields doesn't expose them and the lint can't refuse without them). Authors declare the canonical envelope; lib/mcp/handler-helpers.js#applyDefaultFields merges any missing keys into the user's config before estimate.addService."
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"allOf": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"field": "pricingStrategy",
|
|
17
|
-
"hint": "Pricing model. Accepts shorthand strings
|
|
17
|
+
"hint": "Pricing model. Accepts shorthand strings OR a full object.\n\nShorthand: \"ondemand\", \"spot\", or committed shorthands like \"instanceSavings3yrAllUpfront\", \"computeSavings1yrNoUpfront\".\n\nFull object (recommended for committed pricing):\n- EC2 Instance Savings Plans: {\"model\":\"instanceSavings\",\"term\":\"1 Year\",\"upfrontPayment\":\"None\"}\n- Compute Savings Plans: {\"model\":\"computeSavings\",\"term\":\"1 Year\",\"upfrontPayment\":\"None\"}\n- Standard Reserved Instances (dedicated/host tenancy only): {\"model\":\"reserved\",\"term\":\"1 Year\",\"upfrontPayment\":\"None\"} (alias \"standard\" also accepted)\n- Convertible Reserved Instances (dedicated/host tenancy only): {\"model\":\"convertible\",\"term\":\"1 Year\",\"upfrontPayment\":\"None\"}\n\nValid term values: \"1 Year\", \"3 Year\" (shorthands \"1yr\"/\"3yr\" also accepted). An OMITTED term defaults to \"3 Year\" (the deeper-commitment, cheaper Savings Plan) — pass \"1 Year\" explicitly if you do not want the 3-year commitment. A term that is present but unrecognized is rejected.\nValid upfrontPayment values: \"None\", \"Partial\", \"All\" (case-insensitive; \"No Upfront\" style accepted)\n\nDisplay labels like \"EC2 Instance Savings Plans\" are also accepted and normalized. Any value that does not resolve is REJECTED at add_service with the valid values listed — nothing silently falls back to On-Demand. EstimateBuilder normalizes via lib/aws/ec2.js.",
|
|
18
18
|
"enum": [
|
|
19
19
|
"ondemand",
|
|
20
20
|
"reserved",
|
|
@@ -28,18 +28,15 @@
|
|
|
28
28
|
],
|
|
29
29
|
"traps": [
|
|
30
30
|
"Use serviceCode 'ec2Enhancement' with templateId 'template'. The legacy (eC2Next, quickEstimate) format saves cleanly but renders Read-only — the calculator cannot rehydrate it. EstimateBuilder forces this transformation automatically; do not bypass it.",
|
|
31
|
-
"Reserved Instances (standard, convertible) are HIDDEN under shared tenancy.
|
|
32
|
-
"pricingStrategy
|
|
33
|
-
"
|
|
34
|
-
"The calculator silently demands tenancy, workload, workloadSelection, dataTransferForEC2, detailedMonitoringCheckbox, and ec2AdvancedPricingMetrics — even though the PCT does not mark them validations.required. EstimateBuilder injects all of these via lib/ec2.js#transformConfig.",
|
|
31
|
+
"Reserved Instances (standard, convertible) are HIDDEN under shared tenancy. When you request model \"reserved\"/\"standard\" or \"convertible\" with shared tenancy, EstimateBuilder remaps to instanceSavings/computeSavings (the shared-tenancy equivalents) and emits a trace event. Real Standard/Convertible RIs require tenancy:\"dedicated\" or \"host\".",
|
|
32
|
+
"pricingStrategy values are validated at add_service: unresolvable models/terms/upfrontPayment are rejected with the valid values listed — nothing silently falls back to On-Demand. Accepted terms: '1 Year', '3 Year' (also '1yr'/'3yr', case-insensitive); omitted term defaults to '3 Year' — pass '1 Year' explicitly for the shorter commitment. Accepted upfrontPayment: 'None', 'Partial', 'All' (case-insensitive; 'No Upfront' style accepted).",
|
|
33
|
+
"The calculator silently demands tenancy, workload, workloadSelection, dataTransferForEC2, detailedMonitoringCheckbox, and ec2AdvancedPricingMetrics — even though the PCT does not mark them validations.required. EstimateBuilder injects all of these via lib/aws/ec2.js#transformConfig.",
|
|
35
34
|
"utilization is HIDDEN inside the pricingStrategy component and does NOT appear in get_service_fields' field list. To set utilization, pass top-level utilization: 'N' (string, 1–100); EstimateBuilder routes it into pricingStrategy.value.utilizationValue.",
|
|
36
35
|
"workload is the INSTANCE COUNT — use it for the user's quantity. For 'two instances' set workload: 2 (one entry) or save two entries with workload: 1 each. Either is fine. Do NOT save workload: 1 for 'two instances' and stash the '2' only in description (the saved estimate prices for one instance and the user reads two). Do NOT compensate for utilization with workload (e.g. workload: 0.8 to fake 80% utilization) — the total cost may match but the saved estimate structurally lies about how many instances are running. Use the explicit utilization field instead.",
|
|
37
36
|
"The dataTransferForEC2 field is a composite widget. Its value must be an array of transfer entries, each with entryType, value, unit, and a region field. Valid entryTypes: INBOUND, OUTBOUND, INTRA_REGION.",
|
|
38
37
|
"Unit must be \"tb_month\" or \"gb_month\".",
|
|
39
38
|
"OUTBOUND entries require a \"toRegion\" field — use \"External\" for internet or \"other\" for other AWS regions. INBOUND entries require a \"fromRegion\" field with the same options. INTRA_REGION requires neither.",
|
|
40
|
-
"All three entryTypes (INBOUND, OUTBOUND, INTRA_REGION) must be present in the array even if their value is \"0\"."
|
|
41
|
-
"For Dedicated Hosts (tenancy: \"host\"): instanceType must be the instance FAMILY only (e.g. \"m5\", \"c6g\"), NOT a specific size (e.g. \"m5.metal\", \"m5.xlarge\"). Do NOT set selectedOS — OS is not configured at the host level."
|
|
42
|
-
|
|
39
|
+
"All three entryTypes (INBOUND, OUTBOUND, INTRA_REGION) must be present in the array even if their value is \"0\"."
|
|
43
40
|
],
|
|
44
41
|
"minimalConfig": {
|
|
45
42
|
"region": "us-east-1",
|
|
@@ -52,9 +49,23 @@
|
|
|
52
49
|
"defaultFields": {
|
|
53
50
|
"dataTransferForEC2": {
|
|
54
51
|
"value": [
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
{
|
|
53
|
+
"entryType": "INBOUND",
|
|
54
|
+
"value": "",
|
|
55
|
+
"unit": "tb_month",
|
|
56
|
+
"fromRegion": "External"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"entryType": "OUTBOUND",
|
|
60
|
+
"value": "",
|
|
61
|
+
"unit": "tb_month",
|
|
62
|
+
"toRegion": "External"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"entryType": "INTRA_REGION",
|
|
66
|
+
"value": "",
|
|
67
|
+
"unit": "tb_month"
|
|
68
|
+
}
|
|
58
69
|
]
|
|
59
70
|
}
|
|
60
71
|
}
|