sample-aws-pricing-calculator-mcp 1.0.2 → 1.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 CHANGED
@@ -1,15 +1,18 @@
1
1
  # AWS Pricing Calculator MCP Server
2
2
 
3
- [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that programmatically creates, reads, updates [AWS Pricing Calculator](https://calculator.aws/#/estimate) estimates through natural language.
3
+ [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that creates, reads, and updates [AWS Pricing Calculator](https://calculator.aws/#/estimate) estimates through natural language.
4
4
 
5
5
  [![Install in Kiro](https://img.shields.io/badge/Install-Kiro-9046FF?style=flat-square&logo=kiro)](https://kiro.dev/launch/mcp/add?name=aws-pricing-calculator-mcp-server&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22sample-aws-pricing-calculator-mcp%40latest%22%5D%7D) [![Install in Cursor](https://img.shields.io/badge/Install-Cursor-blue?style=flat-square&logo=cursor)](https://cursor.com/en/install-mcp?name=aws-pricing-calculator-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInNhbXBsZS1hd3MtcHJpY2luZy1jYWxjdWxhdG9yLW1jcEBsYXRlc3QiXX0%3D) [![Install in VS Code](https://img.shields.io/badge/Install-VS_Code-FF9900?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=aws-pricing-calculator-mcp-server&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22sample-aws-pricing-calculator-mcp%40latest%22%5D%7D)
6
6
 
7
7
  ## Key Features
8
8
 
9
- - **Creating Estimates**: Supports all AWS services via live service definitions from the AWS Calculator CDN
10
- - **Importing Estimates**: Download existing estimates by URL/ID as JSON (for modifications e.g. swap AWS regions) or Markdown (for LLM analysis)
11
- - **Batch Processing**: Create estimates from Excel/CSV files via LLM-assisted parsing
12
- - **No AWS Credentials Required**: Works without AWS account access
9
+ - **Build estimates from natural language** - agent constructs the estimate via MCP tools; the server saves it to AWS Pricing Calculator and returns a shareable URL.
10
+ - **No AWS credentials required** - uses public, unauthenticated calculator.aws CDN endpoints.
11
+ - **Live service definitions** - fetches the AWS Pricing Calculator manifest at runtime (~436 services).
12
+ - **Verified Configs Catalog** - 16 per-service entries declaring the smallest config that produces a priced estimate, with documented gotchas.
13
+ - **Lint refusal before save** - refuses estimates the calculator would render read-only or required-input, with actionable recovery hints.
14
+ - **Import existing estimates** - download by URL or ID as JSON (for region swaps, modifications) or Markdown (for LLM analysis).
15
+ - **Two transport modes** - stdio (default, for local clients like Claude Desktop, Kiro, Cursor) and optional HTTP (`MCP_TRANSPORT=http`) for hosted deployments.
13
16
 
14
17
  ## Example
15
18
 
@@ -23,6 +26,25 @@ Output:
23
26
 
24
27
  Requires [Node.js®](https://nodejs.org/en/download).
25
28
 
29
+ ### Via npx (recommended)
30
+
31
+ The published package on npm runs without cloning:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "aws-pricing-calculator-mcp-server": {
37
+ "command": "npx",
38
+ "args": ["-y", "sample-aws-pricing-calculator-mcp@latest"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Add this to your MCP client config (e.g. `~/.kiro/settings/mcp.json`, Claude Desktop's `claude_desktop_config.json`, Cursor's MCP settings, or the VS Code MCP config). The install badges above generate the right config automatically.
45
+
46
+ ### From source
47
+
26
48
  ```bash
27
49
  git clone https://github.com/aws-samples/sample-aws-pricing-calculator-mcp.git
28
50
  cd sample-aws-pricing-calculator-mcp
@@ -30,26 +52,17 @@ npm install
30
52
  npm run build
31
53
  ```
32
54
 
33
- The server communicates over stdio using the MCP protocol — it's designed to be used by MCP-compatible clients (e.g. Claude, Kiro), not called directly via HTTP.
34
-
35
- ### MCP Client Configuration
55
+ Then point your client at the built bundle:
36
56
 
37
- Add to your MCP client config (e.g. `~/.kiro/settings/mcp.json`):
38
-
39
- Using npm/npx (Also always fetches latest version)
40
- ```json
41
- "aws-pricing-calculator-mcp-server" : {
42
- "command" : "npx",
43
- "args" : [ "-y", "sample-aws-pricing-calculator-mcp@latest" ]
44
- }
45
- ```
46
-
47
- From local source (after build)
48
57
  ```json
58
+ {
59
+ "mcpServers": {
49
60
  "aws-pricing-calculator-mcp-server": {
50
61
  "command": "node",
51
62
  "args": ["/path/to/sample-aws-pricing-calculator-mcp/dist/mcp-server.js"]
52
63
  }
64
+ }
65
+ }
53
66
  ```
54
67
 
55
68
  ## MCP Tools
@@ -57,86 +70,117 @@ From local source (after build)
57
70
  | Tool | Description |
58
71
  |---|---|
59
72
  | `search_services` | Search AWS services by name or key. Supports comma-separated queries. |
60
- | `get_service_fields` | Get input field IDs, types, labels, valid options, and selector values for one or more services. |
73
+ | `get_service_fields` | Get input field IDs, types, labels, valid options, and selector values for one or more services. For curated services, the response includes a `catalog` block (`minimalConfig`, required-field hints, traps). For deprecated parent service codes (currently `amazonS3`), returns a `redirect_to_parent` status with `child_service_codes` listing the actual service codes to use instead. |
61
74
  | `create_estimate` | Create a new empty estimate. Returns an estimate ID. |
62
- | `add_service` | Add one or more services to an estimate with config values. Supports batch mode. |
63
- | `export_estimate` | Export an estimate to calculator.aws and get a shareable URL. |
75
+ | `add_service` | Add one or more services to an estimate. Validates field IDs and values against the live service definition (dropdowns, fileSize unit format, numeric/frequency types, region whitelist). Auto-corrects unambiguous mistakes (case mismatches, typos, number-to-string coercion) and returns a `corrections` array on the per-service result. Partial entries return a `partial: true` warning when required inputs are missing. |
76
+ | `validate_estimate` | Dry-run preflight: builds the would-be saved payload and runs a static check, without calling the save API. Returns `{lint_verdict, next_step, lint_services, would_be_payload}`. Use to confirm an estimate would render correctly before saving. |
77
+ | `build_estimate` | One-shot create + add services + lint preflight + save. Returns the shareable URL on success, or a structured envelope identifying which services need field discovery before retry. |
78
+ | `export_estimate` | Save the in-flight estimate to calculator.aws and return a shareable URL. Refuses with an actionable `next_step` if the static linter predicts the saved blob would rehydrate read-only. |
64
79
  | `import_estimate` | Download an existing estimate by URL or ID. Returns JSON (raw) or Markdown. |
65
- | `get_server_info` | Get version and capability information about this MCP server. |
80
+ | `get_server_info` | Return version and capability information. |
66
81
 
67
82
  ## Project Structure
68
83
 
69
84
  ```
70
- mcp-server.js # Entry point — stdio MCP server
85
+ mcp-server.js # Entry point — registers the 9 MCP tools, stdio + HTTP transports
71
86
  lib/
72
- aws-client.js # AWS manifest loading, service definitions, field extraction, save API
73
- estimate-builder.js # Estimate builder with AWS payload generation and export
74
- ec2.js # EC2 config transformation (agent-friendly -> calculator format)
75
- test/
76
- aws-client.test.js # Tests for AWS client
77
- ec2.test.js # Tests for EC2 transform
78
- estimate-builder.test.js # Tests for estimate builder
79
- integration.test.js # Integration tests
80
- validation.test.js # Config validation tests
87
+ aws-client.js # Manifest loading, service definitions, save/read APIs
88
+ estimate-builder.js # In-memory estimate model, AWS payload assembly
89
+ ec2.js # EC2 agent-friendly → ec2Enhancement transform
90
+ validation.js # Pre-save config validation (field-id, value-shape, region, auto-correct)
91
+ can-rehydrate.js # Static rehydration linter (12 predicates, pure)
92
+ can-rehydrate-fetch.js # Network wrapper around the linter
93
+ lint-hints.js # Translates lint failures into agent-actionable next_step text
94
+ catalog.js # Loader for catalog/services/*.json curated entries
95
+ pct-config.js # PCT-based field suggestion
96
+ surfaceability.js # PCT-driven surfaceability index
97
+ agent-fields.js # Synthetic field surfacing for fields hidden in composite widgets
98
+ dom-cost.js # Playwright DOM scrape of the calculator's rendered cost
99
+ handler-helpers.js # Shared internals for the MCP tool handlers
100
+ tool-descriptions.js # Long agent-facing tool descriptions (separated from wiring)
101
+ estimate-store.js # Pluggable in-flight estimate store (memory default)
102
+ estimate-store-dynamodb.js # DynamoDB-backed store for stateless multi-replica deployments
103
+ trace-logger.js # Structured JSON trace events on stderr
104
+ trace-events.js # Trace event name registry
105
+ request-context.js # Per-request session id propagation
106
+ catalog/
107
+ schema.json # JSON Schema for catalog entries
108
+ services/ # Verified configs catalog (minimalConfig, traps, subServices)
109
+ test/ # node:test suite
110
+ eval/ # Scenario-driven behavior eval (87 stdio + LLM scenarios)
111
+ scripts/ # Maintainer tools (catalog authoring, sweep, diagnostic)
112
+ dist/ # Build output: mcp-server.js bundle, aws-calculator.zip, bundle-contract.json
81
113
  ```
82
114
 
115
+ The `lib/` listing is exhaustive at 1.2.0. The architecture diagram below highlights the load-bearing modules; minor helpers are not shown.
116
+
83
117
  ## Build
84
118
 
85
119
  ```bash
86
120
  npm run build
87
121
  ```
88
122
 
89
- Produces `dist/mcp-server.js` — a single-file esbuild bundle (minified, CJS, Node platform).
123
+ Produces:
124
+ - `dist/mcp-server.js` — single-file esbuild bundle (CJS, minified, Node platform)
125
+ - `dist/aws-calculator.zip` — the bundle plus catalog files and runtime libs, zipped for hosted deployment
126
+ - `dist/bundle-contract.json` — describes the bundle's environment-variable surface (name, type, default, enum values) so downstream consumers can typecheck their CDK/Terraform against the actual surface
90
127
 
91
128
  ## Tests
92
129
 
93
- ```bash
94
- npm test
95
- ```
130
+ Three layers, no overlap:
131
+
132
+ - **`npm test`** — node:test suite (424/429 pass with mocked I/O). Per-commit gate. Covers pure functions (validation, lint, surfaceability), payload construction, EC2 transforms, catalog schema.
133
+ - **`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). Run on demand (~1-2 min for stdio scenarios; LLM scenarios cost a few cents on Bedrock Haiku).
135
+
136
+ 424/429 tests pass with mocked I/O (5 skipped, network-dependent). Set `SKIP_NETWORK=1` for offline runs.
96
137
 
97
138
  ## Architecture
98
139
 
99
140
  ```
100
- ┌─────────────────┐ stdio ┌──────────────────────────────────────┐
101
- │ MCP Client │◄──────────────────►│ MCP Server │
102
- │ (Kiro, Claude, │ JSON-RPC over │ │
103
- │ Cursor, etc.) │ stdin/stdout │ mcp-server.js (entry point) │
104
- └─────────────────┘ │ ├── lib/aws-client.js │
105
- │ ├── lib/estimate-builder.js │
106
- │ └── lib/ec2.js │
107
- └──────────┬───────────┬──────────────┘
108
- │ │
109
- HTTPS GET │ │ HTTPS POST
110
- ▼ ▼
111
- ┌──────────────┐ ┌──────────────────┐
112
- │ CloudFront │ │ AWS Calculator │
113
- │ CDN │ │ Save API │
114
- │ │ │ │
115
- │ • manifest │ │ POST /v2/saveAs │
116
- │ • service │ │ → returns │
117
- │ definitions│ │ shareable URL │
118
- └──────────────┘ └──────────────────┘
141
+ ┌─────────────────┐ stdio (default) ┌──────────────────────────────────────┐
142
+ │ MCP Client │◄───────────────────►│ MCP Server │
143
+ │ (Kiro, Claude, │ JSON-RPC over │ │
144
+ │ Cursor, etc.) │ stdin/stdout │ mcp-server.js (entry point) │
145
+ │ │ — or, with │ ├── lib/aws-client.js │
146
+ │ │ MCP_TRANSPORT │ ├── lib/estimate-builder.js │
147
+ │ │ =http, JSON-RPC │ ├── lib/ec2.js (EC2 transform) │
148
+ │ │ over HTTPS │ ├── lib/validation.js │
149
+ └─────────────────┘ │ ├── lib/can-rehydrate.js (lint) │
150
+ │ └── lib/catalog.js │
151
+ └──────┬─────────┬─────────┬──────────┘
152
+ │ │ │
153
+ GET │ GET │ POST │
154
+ ▼ ▼ ▼
155
+ ┌─────────┐ ┌─────────┐ ┌─────────────┐
156
+ │ CDN │ │ CDN │ │ Calculator │
157
+ │ manifest│ │ saved │ │ Save API │
158
+ │ + svc │ │ estimate│ │ │
159
+ │ defs │ │ read │ │ POST │
160
+ │ │ │ │ │ /v2/saveAs │
161
+ │ d1qsj… │ │ d3knq… │ │ dnd5z… │
162
+ └─────────┘ └─────────┘ └─────────────┘
119
163
  ```
120
164
 
121
- - The MCP server runs as a **local child process** spawned by the MCP client. It communicates exclusively over stdio — it is not network-accessible.
122
- - All outbound requests are **HTTPS** to public, unauthenticated AWS CloudFront distributions. No AWS credentials are required or used.
123
- - Estimate data is held **in memory only** and is lost when the process exits. No data is persisted to disk.
165
+ - **Default transport is stdio.** The MCP server runs as a local child process spawned by the MCP client; it is not network-accessible by default. Set `MCP_TRANSPORT=http` to expose it on `PORT` (default `8000`, `HOST` default `127.0.0.1`) for hosted deployments — the operator is responsible for placing it behind appropriate authentication and network controls.
166
+ - All outbound requests are **HTTPS to public, unauthenticated AWS CloudFront distributions** — the same hosts the calculator.aws website uses. No AWS credentials are required for the default in-memory store. The optional DynamoDB store opens a fourth outbound path to DynamoDB and requires AWS credentials with the IAM permissions noted in [Estimate persistence](#estimate-persistence).
167
+ - Estimate data is held **in memory only by default** (lost when the process exits). Switch to `ESTIMATES_STORE=dynamodb` to persist in-flight estimates across replicas.
124
168
 
125
169
  ## How It Works
126
170
 
127
- ### Service Discovery
171
+ ### Service discovery
128
172
 
129
- On first use, the server fetches the AWS Calculator manifest from CloudFront, which contains all 436+ services with their keys, names, and definition URLs. Service definitions are fetched on demand and cached. The `get_service_fields` tool parses these definitions to extract input field IDs, types, labels, and valid options into a flat, usable format.
173
+ On first use, the server fetches the AWS Calculator manifest from CloudFront (~436 services with their keys, names, and definition URLs). Service definitions are fetched on demand and cached. The `get_service_fields` tool parses these definitions to extract input field IDs, types, labels, and valid options into a flat, usable format.
130
174
 
131
- ### Estimate Building
175
+ ### Estimate building
132
176
 
133
- `EstimateBuilder` holds services and groups in memory. When you add a service via `add_service`, config is stored as-is using the AWS field IDs. Services can be organized into named groups, and multiple instances of the same service are supported via composite keys (e.g. `aWSLambda:Compute`).
177
+ `EstimateBuilder` holds services and groups in memory (or in DynamoDB if configured). When `add_service` is called, the config is validated against the live service definition and stored using the AWS field IDs. Services can be organized into named groups, and multiple instances of the same service are supported via composite keys (e.g. `aWSLambda:Compute`).
134
178
 
135
- ### EC2 Handling
179
+ ### EC2 handling
136
180
 
137
- 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. This includes support for On-Demand, Savings Plans, Reserved Instances, and Spot pricing.
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.
138
182
 
139
- ### Partition Support
183
+ ### Partition support
140
184
 
141
185
  The server supports three AWS partitions:
142
186
  - `aws` — standard commercial regions
@@ -145,62 +189,207 @@ The server supports three AWS partitions:
145
189
 
146
190
  ### Export to calculator.aws
147
191
 
148
- When `export_estimate` is called, the builder:
192
+ When `export_estimate` is called, the server:
149
193
 
150
- 1. Resolves each service name against the manifest
151
- 2. Fetches the service definition to get the correct `version`, `serviceCode`, and template ID
152
- 3. Maps config keys to `calculationComponents` in the AWS payload format
153
- 4. POSTs the assembled payload to the AWS Calculator save API
154
- 5. Returns the shareable `calculator.aws` URL
194
+ 1. Resolves each service name against the manifest.
195
+ 2. Fetches the service definition to get the correct `version`, `serviceCode`, and template ID.
196
+ 3. Maps config keys to `calculationComponents` in the AWS payload format.
197
+ 4. Runs the rehydration linter against the would-be saved blob. If it predicts read-only, the call is refused with an actionable recovery hint instead of a URL.
198
+ 5. POSTs the payload to the AWS Calculator save API.
199
+ 6. Returns the shareable `calculator.aws` URL.
155
200
 
156
201
  AWS recalculates the actual costs when someone opens the link.
157
202
 
158
203
  ## Environment Variables
159
204
 
160
- All optional:
205
+ All optional. The full set is also documented machine-readably in `dist/bundle-contract.json` after build.
161
206
 
162
207
  | Variable | Default | Purpose |
163
208
  |---|---|---|
164
- | `AWS_MANIFEST_URL` | CloudFront manifest URL | AWS service catalog |
165
- | `AWS_SAVE_URL` | CloudFront save URL | Estimate persistence |
209
+ | `AWS_SAVE_URL` | CloudFront save URL | Override the AWS Calculator save endpoint (testing only). |
210
+ | `MCP_TRANSPORT` | `stdio` | `stdio` or `http`. HTTP listens on `PORT` (default `8000`) bound to `HOST` (default `127.0.0.1`). |
211
+ | `ESTIMATES_STORE` | `memory` | In-flight estimate store. See [Estimate persistence](#estimate-persistence). |
212
+ | `ESTIMATES_TABLE` | — | DynamoDB table name. Required when `ESTIMATES_STORE=dynamodb`. |
213
+ | `ESTIMATES_TTL_SECONDS` | `0` | TTL for DynamoDB-stored estimates in seconds. `0` = no TTL. |
214
+ | `TRACE` | `off` | Set to `on` (or `1`/`true`/`yes`) to enable structured stderr trace events. See [Trace events](#trace-events). |
215
+ | `TRACE_RESULT_TEXT_MAX` | `500` | Cap on `resultText` length when tracing is enabled. |
216
+
217
+ ## Estimate persistence
218
+
219
+ The MCP server keeps in-flight estimates (between `create_estimate` and `export_estimate`) in a pluggable store. Configure with `ESTIMATES_STORE`:
220
+
221
+ | Value | Description |
222
+ | --- | --- |
223
+ | `memory` (default) | In-process `Map`. State is lost when the process exits. Suitable for stdio and single-process deployments. |
224
+ | `dynamodb` | Persists snapshots in a DynamoDB table. Required for any deployment where `create_estimate` and `add_service` may run in different processes (multi-replica HTTP, Lambda, container runtimes with non-sticky session routing). |
225
+
226
+ ### `dynamodb` configuration
227
+
228
+ The AWS SDK packages are NOT bundled with the published artifact (they are declared as optional peer dependencies). Before running with `ESTIMATES_STORE=dynamodb`, install them in your deployment image:
229
+
230
+ ```bash
231
+ npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
232
+ ```
233
+
234
+ Required env vars when `ESTIMATES_STORE=dynamodb`:
235
+
236
+ | Variable | Required | Description |
237
+ | --- | --- | --- |
238
+ | `ESTIMATES_TABLE` | Yes | DynamoDB table name. |
239
+ | `AWS_REGION` | Yes | AWS region for the DynamoDB client. |
240
+ | `ESTIMATES_TTL_SECONDS` | No | If set, items are written with an `expiresAt` attribute equal to `now + ESTIMATES_TTL_SECONDS`. Pair this with a TTL configuration on the table (TTL attribute name: `expiresAt`) to expire abandoned estimates automatically. |
241
+
242
+ ### Table schema
243
+
244
+ ```
245
+ TableName: <ESTIMATES_TABLE>
246
+ PartitionKey: id (String)
247
+ Attributes:
248
+ id (String) the estimate's UUID
249
+ snapshot (String) JSON-encoded snapshot of the estimate
250
+ expiresAt (Number) optional epoch seconds, populated when ESTIMATES_TTL_SECONDS is set
251
+ TTL attribute: expiresAt (configure on the table if you set ESTIMATES_TTL_SECONDS)
252
+ ```
253
+
254
+ The IAM role running the server needs `dynamodb:GetItem`, `dynamodb:PutItem`, and `dynamodb:DeleteItem` on the table.
255
+
256
+ ### Implementation notes
257
+
258
+ - Each estimate is stored as a single JSON string under one item. Estimates that approach DynamoDB's 400 KB per-item cap (hundreds of services) will fail to write — use S3 or chunk-encoding if you need that.
259
+ - The store is constructed once at process start. Switching backends requires restarting the process.
260
+
261
+ ## Verified Configs Catalog
262
+
263
+ The catalog (`catalog/services/*.json`) is a set of curated per-service entries. Each entry declares:
264
+
265
+ - `minimalConfig` — the smallest config that produces a priced editable estimate
266
+ - `required[]` — pricing-engine-required fields (often broader than the manifest's `validations.required: true`)
267
+ - `optional[]` — documented hints with examples
268
+ - `traps[]` — free-text gotchas the manifest can't express (e.g. "Step Functions' frequency unit MUST be `perMonth`, not `millionPerMonth`")
269
+ - `subServices[]` — child routing for sub-service-selector parents (e.g. S3 storage classes, Bedrock models)
270
+
271
+ `get_service_fields` returns this metadata in a `catalog` block when an entry exists, so the agent doesn't have to guess.
272
+
273
+ ### Coverage
274
+
275
+ | Status | Count | Meaning |
276
+ |---|---|---|
277
+ | `verified` | 16 | Reverified end-to-end: a fresh save from `minimalConfig` renders non-zero cost in calculator.aws |
278
+ | `partial` | 1 | Authored but not fully verified |
279
+ | **Total entries** | **17** | Out of ~436 services in the manifest |
280
+
281
+ The verified entries cover the highest-impact services from production usage analysis: Lambda, RDS PostgreSQL, ALB, Fargate, EC2, NAT Gateway, Transit Gateway, CloudTrail, Direct Connect, Cognito, S3 (storage classes), SNS, SageMaker, Bedrock, AppSync, and Step Functions.
282
+
283
+ ### Why not catalog every service
284
+
285
+ Catalog entries are work to author and maintain. They earn their place when the manifest underdeclares what the pricing engine actually needs (the silent-$0 trap class). For services where the manifest is sufficient, no entry is needed — the agent reads the manifest, sends fields, the calculator prices them.
286
+
287
+ `scripts/diagnose-service.js` runs the falsifiable test: probe each un-cataloged service and classify it. Useful when scoping new catalog work.
288
+
289
+ ### Validating the catalog
290
+
291
+ ```bash
292
+ npm run validate-catalog # JSON Schema check (runs in npm test)
293
+ npm run validate-catalog:cost # Sweeps verified entries against the live cost oracle
294
+ ```
295
+
296
+ The cost-oracle sweep saves a fresh estimate from each verified entry's `minimalConfig` and confirms it renders ≥$0.01/mo via the DOM oracle (`lib/dom-cost.js`). Run on a cadence (manual / cron / pre-deploy) to catch stale URLs and pricing-engine drift.
297
+
298
+ ### Catalog quality
299
+
300
+ The 1.2.0 catalog content was audited along three independent axes before release.
301
+
302
+ **Cost-rendering correctness (deterministic).** The `validate-catalog:cost` sweep saves each of the 16 verified entries' `minimalConfig` and confirms a non-zero rendered cost via the DOM oracle. Result: **16/16 entries render priced cost** (e.g. Lambda $699.80/mo, RDS PostgreSQL $175.75/mo, EC2 $70.08/mo).
303
+
304
+ **Trap content audit (manifest cross-check + LLM cold-eye).** Each `traps[]` claim was checked against the live manifest data and, where applicable, the saved blob of the corresponding `verifiedEstimateId`. Result across the 60 shipped traps: **0 hallucinated claims**. Every trap was then classified by the type of utility it provides to a runtime agent — falling roughly into:
305
+
306
+ - **Prevents save-failure** — without the trap, the agent's save fails (lint refusal, value validation, sub-service-selector rejected). The largest category.
307
+ - **Prevents silent-zero** — save+lint pass but cost renders $0 without the trap. The catalog's most distinctive value class.
308
+ - **Steers selection** — empirically demonstrated by a probe scenario, manifest-derived (mechanism visible to `get_service_fields`), or a well-known AWS pricing fact (Multi-AZ doubles cost, etc.).
309
+
310
+ 5 traps that classified as "author-only" or "unverifiable prose" were removed before release rather than shipped to agents.
311
+
312
+ **Agent-success measurement (empirical, paired drift audits).** The `eval/scenarios/cat-<svc>-with.yaml` / `cat-<svc>-without.yaml` pairs run the same prompt twice — once with the catalog active, once with it removed — and compare outcomes. As of the 1.2.0 eval run with Bedrock Haiku 4.5: **6 services demonstrate measurably better agent success with the catalog** (ALB, CloudTrail, Direct Connect, EC2, Fargate, Transit Gateway — `with` passes, `without` fails). 2 are redundant (manifest alone is sufficient: AppSync, NAT). 8 produce mixed results that depend on agent prompt-following behavior; for those services the catalog still provides correct content (verified by the cost sweep above) but the marginal effect on a specific LLM run isn't deterministic.
313
+
314
+ The trap audit and earns-place data are summarized here for transparency; the deterministic cost sweep is the load-bearing correctness gate.
166
315
 
167
316
  ## Known Issues
168
317
 
169
318
  - The CloudFront save/manifest APIs are undocumented and may change without notice.
170
319
  - Callers must use the correct AWS field IDs — discover them via `get_service_fields`.
171
- - While the tool discovers applicable selectorValues, it currently cannot resolve dependencies e.g. Instance Types <-> License
172
- - Estimates live in memory and don't persist across restarts.
173
- - No local cost calculation — pricing is computed by AWS when viewing the shareable link -> Make sure to press `Update estimate` to reflect latest pricing.
174
- - Only https://calculator.aws/ supported for now
320
+ - The server discovers applicable selector values but cannot resolve cross-field dependencies (e.g. EC2 Instance Types ↔ License).
321
+ - With the default `memory` store, in-flight estimates don't persist across restarts. Use `ESTIMATES_STORE=dynamodb` for stateless or multi-replica deployments.
322
+ - No local cost calculation — pricing is computed by AWS when viewing the shareable link. Press **Update estimate** in the calculator UI to reflect the latest pricing.
323
+ - Only `https://calculator.aws/` is supported.
324
+ - Catalog coverage is partial (16 verified entries against ~436 manifest services). Uncatalogued services still benefit from the lint and trace events; they just don't get the magnitude-calibration role the catalog plays.
175
325
 
176
326
  ## Security
177
327
 
178
328
  This is sample code intended for educational purposes. You should work with your security and legal teams to meet your organizational security, regulatory, and compliance requirements before deployment.
179
329
 
180
- ### Security Model
330
+ ### Security model
181
331
 
182
- This MCP server is a **local tool provider** — it runs as a child process of an MCP client and is not network-accessible. It has no authentication or authorization layer; access control is the responsibility of the MCP client that spawns it.
332
+ This MCP server is a **local tool provider** — by default it runs as a child process of an MCP client over stdio and is not network-accessible. It has no authentication or authorization layer; access control is the responsibility of the MCP client that spawns it. When `MCP_TRANSPORT=http` is enabled, the operator is responsible for placing the server behind appropriate authentication and network controls.
183
333
 
184
- The server does not handle AWS credentials, customer data, or PII. It processes only pricing configuration parameters (e.g., region, instance type, request counts) provided by the MCP client.
334
+ The server does not handle AWS credentials (in the default `memory` mode), customer data, or PII. It processes only pricing configuration parameters (region, instance type, request counts) provided by the MCP client. The DynamoDB store does require AWS credentials with the listed IAM permissions; the server itself never reads or stores the calling user's AWS account data.
185
335
 
186
- These are the same public, unauthenticated endpoints used by the [calculator.aws](https://calculator.aws) website. No AWS credentials are transmitted.
336
+ The save and manifest endpoints are the same public, unauthenticated CloudFront distributions used by the [calculator.aws](https://calculator.aws) website.
187
337
 
188
- ### Input Validation and Sanitization
338
+ ### Input validation and sanitization
189
339
 
190
340
  - All MCP tool inputs are validated using [Zod](https://zod.dev/) schemas before processing.
191
341
  - User-provided descriptions and group names are sanitized to remove `<`, `>`, and `&` characters before inclusion in API payloads, preventing HTML/XML injection in the calculator frontend.
192
- - Service configuration keys are validated against AWS service definitions with typo detection (Levenshtein distance), rejecting invalid field IDs before they reach the API.
342
+ - Service configuration keys and values are validated against the live service definition (typo detection via Levenshtein distance, dropdown options against enums, fileSize unit format, region whitelist) before payload construction.
193
343
 
194
- ### Data Handling
344
+ ### Data handling
195
345
 
196
- - Estimate data is held **in memory only** for the lifetime of the process. No data is written to disk or persisted across restarts.
346
+ - Estimate data is held **in memory only by default** for the lifetime of the process. With `ESTIMATES_STORE=dynamodb`, estimates are persisted to a customer-owned DynamoDB table (the operator owns the data lifecycle).
197
347
  - The data consists of pricing configuration (region codes, service parameters, instance types) — not secrets, credentials, or personally identifiable information.
198
- - Shareable URLs generated by the export contain only an opaque estimate ID. The estimate content is stored by AWS, not by this server.
348
+ - Shareable URLs generated by export contain only an opaque estimate ID. The estimate content is stored by AWS, not by this server.
199
349
 
200
- ### Reporting Security Issues
350
+ ### Reporting security issues
201
351
 
202
352
  See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for information on reporting security issues.
203
353
 
354
+ ## Trace events
355
+
356
+ When tracing is enabled (`TRACE=on`), the server emits structured JSON lines on stderr, one per event, for every tool call and save operation. Hosted deployments typically capture these via the runtime's log pipeline; in stdio mode they appear on the process stderr directly.
357
+
358
+ **Tracing is off by default.** Set `TRACE=on` (or `1`/`true`/`yes`) to enable. The default keeps stderr quiet for local CLI use.
359
+
360
+ Each line carries `ts` (ISO timestamp), `event` (one of the names below), and an `mcpSessionId` field when the call was made over HTTP transport.
361
+
362
+ | Event | Payload fields | When emitted |
363
+ |---|---|---|
364
+ | `tool.call` | `name`, `args`, `mcpSessionId?` | Tool handler entry |
365
+ | `tool.result` | `name`, `isError`, `resultText` (≤500 chars), `resultLength` (pre-truncation), `durationMs`, `mcpSessionId?` | Tool handler exit (success or error) |
366
+ | `lint` | `verdict`, `services`, `estimateId?`, `mcpSessionId?` | After the rehydration linter runs in `export_estimate` / `build_estimate` |
367
+ | `save.send` | `bytes`, `groupCount`, `serviceCount`, `estimateId?`, `mcpSessionId?` | Before POST to AWS save API |
368
+ | `save.ok` | `savedKey`, `estimateId?`, `mcpSessionId?` | Save API returned 200 |
369
+ | `save.fail` | `status`, `body` (≤500 chars), `estimateId?`, `mcpSessionId?` | Save API returned non-200 |
370
+ | `session.start` | `mcpSessionId?` | New MCP session opened (HTTP transport) |
371
+
372
+ `tool.call` and `tool.result` are paired — sort by `ts` to reconstruct the sequence. `save.*` events sit between them on `export_estimate` / `build_estimate` calls.
373
+
374
+ `resultText` is truncated to keep log volume bounded; `resultLength` is the original (pre-truncation) length. Set `TRACE_RESULT_TEXT_MAX=<N>` to raise the cap when investigating a clipped response.
375
+
376
+ ### Customer-text redaction
377
+
378
+ Trace events are typically captured to log groups that may be queryable by anyone with read access. To keep agent-supplied free text out of those logs, the trace logger redacts values at known-sensitive keys before emit:
379
+
380
+ - `name` — estimate names from `create_estimate` / `build_estimate`
381
+ - `description` — per-service free-text labels from `add_service` / `build_estimate` / `import_estimate`
382
+
383
+ 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.
384
+
385
+ 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`.
386
+
387
+ The `mcpSessionId` field has three states, useful for telling transports apart:
388
+
389
+ - HTTP request with an `mcp-session-id` header → `"mcpSessionId": "<uuid>"`
390
+ - HTTP request without the header → `"mcpSessionId": null`
391
+ - Stdio transport → field omitted entirely
392
+
204
393
  ## AWS MCP Servers Comparison: Pricing & Cost Management
205
394
 
206
395
  | | **AWS Pricing Calculator MCP (This)** | **[AWS Billing & Cost Management MCP](https://github.com/awslabs/mcp#-cost--operations)** | **[AWS Pricing MCP](https://github.com/awslabs/mcp#-cost--operations)** |
@@ -212,26 +401,11 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for information
212
401
  | **Scope** | Forward-looking estimates | Historical & current spend | Current catalog pricing |
213
402
  | **AWS Credentials** | Not required (uses public calculator API) | Required (reads your billing data) | Required (`pricing:*` permissions) |
214
403
 
215
- TL;DR: Use the Pricing Calculator MCP to build estimates for proposals, the Billing & Cost Management MCP to analyze/optimize what you're already spending, and the Pricing MCP for granular unit-price lookups and IaC cost analysis.
404
+ TL;DR: Use the Pricing Calculator MCP to build estimates for proposals, the Billing & Cost Management MCP to analyze and optimize existing spend, and the Pricing MCP for granular unit-price lookups and IaC cost analysis.
216
405
 
217
406
  ## Changelog
218
407
 
219
- ### [1.0.2] - 2026-05-13
220
- - Added to npm https://www.npmjs.com/package/sample-aws-pricing-calculator-mcp
221
- - Added quick install button (Kiro, Cursor, VS Code)
222
-
223
- ### [1.0.1] - 2026-05-13
224
- - Fixed Bug: Proper support for nested Structures e.g. Elasticache, RDS, Bedrock, ALB
225
- - Fixed Bug: EC2/EBS iops, throughput not recognized
226
- - Enriched field metadata with allowed values, also validates upon submission - yet dependencies are not resolved
227
- - Costs are now displayed on initial load - however pressing 'Update estimate' is recommended
228
- - Supports importing/reading estimates
229
- - Dependencies updated
230
- - Removed dead code
231
- - Version info added (get_server_info)
232
-
233
- ### [1.0.0] - 2026-04-30
234
- - Initial Release
408
+ See [CHANGELOG.md](CHANGELOG.md) for the full release history.
235
409
 
236
410
  ## License
237
411
 
@@ -239,4 +413,4 @@ This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) fil
239
413
 
240
414
  ## Disclaimer
241
415
 
242
- Before using an MCP Server, you should consider conducting your own independent assessment to ensure that your use would comply with your own specific security and quality control practices and standards, as well as the laws, rules, and regulations that govern you and your content.
416
+ Before using an MCP Server, you should consider conducting your own independent assessment to ensure that your use would comply with your own specific security and quality control practices and standards, as well as the laws, rules, and regulations that govern you and your content.
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "AWS Pricing Calculator Service Catalog Entry",
4
+ "type": "object",
5
+ "required": ["serviceCode", "displayName", "status", "minimalConfig"],
6
+ "properties": {
7
+ "$schema": { "type": "string" },
8
+ "serviceCode": {
9
+ "type": "string",
10
+ "pattern": "^[a-zA-Z][a-zA-Z0-9]*$",
11
+ "description": "Canonical service code from manifest/en_US.json (e.g. aWSLambda, ec2Enhancement)."
12
+ },
13
+ "displayName": { "type": "string", "minLength": 1 },
14
+ "templateId": {
15
+ "type": "string",
16
+ "description": "The estimateFor value the calculator expects. Optional for parent envelopes whose children carry their own template ids."
17
+ },
18
+ "status": {
19
+ "type": "string",
20
+ "enum": ["verified", "partial", "unverified", "broken"],
21
+ "description": "verified = round-trip test passes; partial = saves+renders but with Required-input; unverified = not yet probed; broken = known to fail."
22
+ },
23
+ "lastVerifiedAt": {
24
+ "type": "string",
25
+ "format": "date",
26
+ "description": "ISO date of the most recent successful round-trip. Required when status is 'verified' or 'partial'."
27
+ },
28
+ "verifiedEstimateId": {
29
+ "type": "string",
30
+ "pattern": "^[a-f0-9]{40}$",
31
+ "description": "SHA1 estimate id from the most recent successful round-trip. Required when status is 'verified' or 'partial'."
32
+ },
33
+ "required": {
34
+ "type": "array",
35
+ "items": { "$ref": "#/definitions/field" },
36
+ "default": []
37
+ },
38
+ "traps": {
39
+ "type": "array",
40
+ "items": { "type": "string", "minLength": 1 },
41
+ "description": "Human-readable gotchas an agent or author should know. Free-form prose.",
42
+ "default": []
43
+ },
44
+ "subServices": {
45
+ "type": "array",
46
+ "items": { "$ref": "#/definitions/subService" },
47
+ "description": "For parent envelopes only — describes each child's serviceCode, estimateFor, and required fields.",
48
+ "default": []
49
+ },
50
+ "minimalConfig": {
51
+ "type": "object",
52
+ "description": "The smallest config that produces an editable estimate. For parent envelopes, keyed by sub-service code; for plain services, the config object itself."
53
+ },
54
+ "defaultFields": {
55
+ "type": "object",
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."
58
+ }
59
+ },
60
+ "allOf": [
61
+ {
62
+ "if": { "required": ["status"], "properties": { "status": { "enum": ["verified", "partial"] } } },
63
+ "then": { "required": ["lastVerifiedAt", "verifiedEstimateId"] }
64
+ }
65
+ ],
66
+ "definitions": {
67
+ "field": {
68
+ "type": "object",
69
+ "required": ["field", "hint"],
70
+ "properties": {
71
+ "field": { "type": "string", "minLength": 1 },
72
+ "hint": { "type": "string", "minLength": 1 },
73
+ "shape": { "type": "string" },
74
+ "enum": { "type": "array", "items": { "type": "string" } },
75
+ "default": {},
76
+ "example": {}
77
+ }
78
+ },
79
+ "subService": {
80
+ "type": "object",
81
+ "required": ["serviceCode", "estimateFor"],
82
+ "properties": {
83
+ "serviceCode": { "type": "string", "minLength": 1 },
84
+ "estimateFor": { "type": "string", "minLength": 1 },
85
+ "required": {
86
+ "type": "array",
87
+ "items": { "$ref": "#/definitions/field" },
88
+ "default": []
89
+ },
90
+ "productCodes": {
91
+ "type": "array",
92
+ "items": { "type": "string", "minLength": 1 },
93
+ "description": "Manifest service codes (subType:'subService') that route under this provider but are not in the parent's templates[]. Lets the lint hint redirect agents from a product code (e.g. titanTextEmbeddingsV2) to the provider code (e.g. amazon) that the calculator's parent envelope actually claims."
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }