@cyanheads/eia-energy-mcp-server 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.
Files changed (72) hide show
  1. package/CLAUDE.md +351 -0
  2. package/Dockerfile +99 -0
  3. package/LICENSE +195 -0
  4. package/README.md +274 -0
  5. package/changelog/0.1.x/0.1.0.md +18 -0
  6. package/changelog/0.1.x/0.1.1.md +42 -0
  7. package/changelog/0.1.x/0.1.2.md +22 -0
  8. package/changelog/0.1.x/0.1.3.md +17 -0
  9. package/changelog/0.1.x/0.1.4.md +17 -0
  10. package/changelog/0.1.x/0.1.5.md +19 -0
  11. package/changelog/0.1.x/0.1.6.md +19 -0
  12. package/changelog/0.1.x/0.1.7.md +11 -0
  13. package/changelog/0.2.x/0.2.0.md +22 -0
  14. package/changelog/template.md +93 -0
  15. package/dist/config/server-config.d.ts +18 -0
  16. package/dist/config/server-config.d.ts.map +1 -0
  17. package/dist/config/server-config.js +36 -0
  18. package/dist/config/server-config.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +39 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/mcp-server/tools/definitions/browse-routes.tool.d.ts +28 -0
  24. package/dist/mcp-server/tools/definitions/browse-routes.tool.d.ts.map +1 -0
  25. package/dist/mcp-server/tools/definitions/browse-routes.tool.js +72 -0
  26. package/dist/mcp-server/tools/definitions/browse-routes.tool.js.map +1 -0
  27. package/dist/mcp-server/tools/definitions/dataframe-describe.tool.d.ts +34 -0
  28. package/dist/mcp-server/tools/definitions/dataframe-describe.tool.d.ts.map +1 -0
  29. package/dist/mcp-server/tools/definitions/dataframe-describe.tool.js +114 -0
  30. package/dist/mcp-server/tools/definitions/dataframe-describe.tool.js.map +1 -0
  31. package/dist/mcp-server/tools/definitions/dataframe-drop.tool.d.ts +22 -0
  32. package/dist/mcp-server/tools/definitions/dataframe-drop.tool.d.ts.map +1 -0
  33. package/dist/mcp-server/tools/definitions/dataframe-drop.tool.js +56 -0
  34. package/dist/mcp-server/tools/definitions/dataframe-drop.tool.js.map +1 -0
  35. package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts +28 -0
  36. package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts.map +1 -0
  37. package/dist/mcp-server/tools/definitions/dataframe-query.tool.js +124 -0
  38. package/dist/mcp-server/tools/definitions/dataframe-query.tool.js.map +1 -0
  39. package/dist/mcp-server/tools/definitions/describe-route.tool.d.ts +58 -0
  40. package/dist/mcp-server/tools/definitions/describe-route.tool.d.ts.map +1 -0
  41. package/dist/mcp-server/tools/definitions/describe-route.tool.js +164 -0
  42. package/dist/mcp-server/tools/definitions/describe-route.tool.js.map +1 -0
  43. package/dist/mcp-server/tools/definitions/query-route.tool.d.ts +66 -0
  44. package/dist/mcp-server/tools/definitions/query-route.tool.d.ts.map +1 -0
  45. package/dist/mcp-server/tools/definitions/query-route.tool.js +264 -0
  46. package/dist/mcp-server/tools/definitions/query-route.tool.js.map +1 -0
  47. package/dist/mcp-server/tools/definitions/search-routes.tool.d.ts +23 -0
  48. package/dist/mcp-server/tools/definitions/search-routes.tool.d.ts.map +1 -0
  49. package/dist/mcp-server/tools/definitions/search-routes.tool.js +94 -0
  50. package/dist/mcp-server/tools/definitions/search-routes.tool.js.map +1 -0
  51. package/dist/services/canvas-bridge/canvas-bridge.d.ts +68 -0
  52. package/dist/services/canvas-bridge/canvas-bridge.d.ts.map +1 -0
  53. package/dist/services/canvas-bridge/canvas-bridge.js +206 -0
  54. package/dist/services/canvas-bridge/canvas-bridge.js.map +1 -0
  55. package/dist/services/canvas-bridge/sql-gate-extras.d.ts +13 -0
  56. package/dist/services/canvas-bridge/sql-gate-extras.d.ts.map +1 -0
  57. package/dist/services/canvas-bridge/sql-gate-extras.js +37 -0
  58. package/dist/services/canvas-bridge/sql-gate-extras.js.map +1 -0
  59. package/dist/services/eia/eia-service.d.ts +72 -0
  60. package/dist/services/eia/eia-service.d.ts.map +1 -0
  61. package/dist/services/eia/eia-service.js +497 -0
  62. package/dist/services/eia/eia-service.js.map +1 -0
  63. package/dist/services/eia/route-cache.d.ts +65 -0
  64. package/dist/services/eia/route-cache.d.ts.map +1 -0
  65. package/dist/services/eia/route-cache.js +168 -0
  66. package/dist/services/eia/route-cache.js.map +1 -0
  67. package/dist/services/eia/types.d.ts +115 -0
  68. package/dist/services/eia/types.d.ts.map +1 -0
  69. package/dist/services/eia/types.js +7 -0
  70. package/dist/services/eia/types.js.map +1 -0
  71. package/package.json +104 -0
  72. package/server.json +163 -0
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ <div align="center">
2
+ <h1>@cyanheads/eia-energy-mcp-server</h1>
3
+ <p><b>Browse and query the U.S. Energy Information Administration API v2 — electricity, petroleum, natural gas, coal, forecasts, and more via MCP. STDIO or Streamable HTTP.</b>
4
+ <div>7 Tools</div>
5
+ </p>
6
+ </div>
7
+
8
+ <div align="center">
9
+
10
+ [![Version](https://img.shields.io/badge/Version-0.2.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/eia-energy-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/eia-energy-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/eia-energy-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
+
12
+ [![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/eia-energy-mcp-server/releases/latest/download/eia-energy-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=eia-energy-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvZWlhLWVuZXJneS1tY3Atc2VydmVyIl0sImVudiI6eyJFSUFfQVBJX0tFWSI6InlvdXItYXBpLWtleSJ9fQ==) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22eia-energy-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads/eia-energy-mcp-server%22%5D%2C%22env%22%3A%7B%22EIA_API_KEY%22%3A%22your-api-key%22%7D%7D)
13
+
14
+ [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
15
+
16
+ </div>
17
+
18
+ <div align="center">
19
+
20
+ **Public Hosted Server:** [https://eia-energy.caseyjhand.com/mcp](https://eia-energy.caseyjhand.com/mcp)
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## Tools
27
+
28
+ Seven tools covering the two-phase EIA workflow — find the right dataset route, pull the data, and query spilled results via SQL:
29
+
30
+ | Tool | Description |
31
+ |:-----|:------------|
32
+ | `eia_browse_routes` | Lists child routes under a given path in the EIA dataset taxonomy. Start at root to see top-level categories, then drill into subcategories and leaf routes. |
33
+ | `eia_describe_route` | Returns full metadata for a leaf route: available facets with valid values, data column names, frequency options, units, and date range. Call before `eia_query_route` to understand filter options. |
34
+ | `eia_search_routes` | Fuzzy text search across route names, descriptions, and category labels. Resolves natural-language queries like "gasoline retail prices" or "solar capacity by state" to matching route paths. |
35
+ | `eia_query_route` | Fetches data from a leaf route with optional facet filters, date range, frequency, and column selection. Spills large result sets to a DataCanvas table for SQL analysis. |
36
+ | `eia_dataframe_describe` | Lists active DataCanvas dataframes created by prior `eia_query_route` calls. Shows table name, column names and types, row count, and canvas ID. |
37
+ | `eia_dataframe_query` | Runs a read-only SQL SELECT against a DataCanvas dataframe by canvas ID or table name. |
38
+ | `eia_dataframe_drop` | Drops a DataCanvas dataframe, freeing its memory. Only exposed when `EIA_DATAFRAME_DROP_ENABLED=true`. |
39
+
40
+ ### `eia_browse_routes`
41
+
42
+ Walk the EIA dataset taxonomy from root to leaf.
43
+
44
+ - Root call returns 14 top-level categories: electricity, petroleum, natural-gas, coal, international, total-energy, steo, aeo, ieo, seds, crude-oil-imports, nuclear-outages, densified-biomass, co2-emissions
45
+ - Intermediate paths return subcategories; leaf routes are flagged so callers know when to switch to `eia_describe_route`
46
+ - `STEO` (Short-Term Energy Outlook) is a flat leaf with 1,469 named series — no sub-routes
47
+
48
+ ---
49
+
50
+ ### `eia_describe_route`
51
+
52
+ Full schema for a leaf route. Required before constructing facet filters.
53
+
54
+ - Returns facets with valid values (fetched via per-facet API calls and cached in-process)
55
+ - Returns data column names, units, frequency options, and date range
56
+ - `eia_search_routes` and `eia_browse_routes` resolve the route path; this tool provides the filter vocabulary
57
+
58
+ ---
59
+
60
+ ### `eia_search_routes`
61
+
62
+ Fuzzy search across the in-memory route index.
63
+
64
+ - Indexes route names, descriptions, and category labels — plus STEO's 1,469 series names
65
+ - Resolves natural language ("natural gas spot prices", "ethanol net imports") to queryable route paths
66
+ - Route tree is cached in-process at first call; subsequent searches hit the Fuse.js index with no upstream cost
67
+
68
+ ---
69
+
70
+ ### `eia_query_route`
71
+
72
+ Pull data from a leaf route.
73
+
74
+ - Facet filters keyed by facet ID (e.g. `{ "stateid": "TX", "sectorid": ["RES", "COM"] }`)
75
+ - Date range and frequency selection; valid values discoverable via `eia_describe_route`
76
+ - Pagination via `offset`/`length` (max 5,000 rows per page); total row count in response
77
+ - All numeric values arrive as strings from the EIA API — units appear as inline `{col}-units` fields per row
78
+ - DataCanvas spillover when result set exceeds `length`: returns `canvas_id` for SQL queries over the full dataset
79
+
80
+ ## Features
81
+
82
+ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
83
+
84
+ - Declarative tool definitions — single file per tool, framework handles registration and validation
85
+ - Unified error handling — handlers throw, framework catches, classifies, and formats
86
+ - Pluggable auth: `none`, `jwt`, `oauth`
87
+ - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
88
+ - Structured logging with optional OpenTelemetry tracing
89
+ - STDIO and Streamable HTTP transports
90
+
91
+ EIA-specific:
92
+
93
+ - Full coverage of EIA API v2 — all 14 top-level dataset categories
94
+ - In-process route tree cache with Fuse.js fuzzy index — built once at startup, no repeated upstream calls
95
+ - Per-route facet cache via `Promise.all` fan-out — valid filter values available without re-fetching
96
+ - STEO series names (1,469 entries) indexed for natural-language discovery
97
+ - DataCanvas (DuckDB) opt-in for tabular spillover — graceful degradation when unavailable
98
+
99
+ ## Getting started
100
+
101
+ Get a free API key at [api.eia.gov](https://www.eia.gov/opendata/), then add the following to your MCP client configuration file.
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "eia": {
107
+ "type": "stdio",
108
+ "command": "bunx",
109
+ "args": ["@cyanheads/eia-energy-mcp-server@latest"],
110
+ "env": {
111
+ "MCP_TRANSPORT_TYPE": "stdio",
112
+ "MCP_LOG_LEVEL": "info",
113
+ "EIA_API_KEY": "your-api-key"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ Or with npx (no Bun required):
121
+
122
+ ```json
123
+ {
124
+ "mcpServers": {
125
+ "eia": {
126
+ "type": "stdio",
127
+ "command": "npx",
128
+ "args": ["-y", "@cyanheads/eia-energy-mcp-server@latest"],
129
+ "env": {
130
+ "MCP_TRANSPORT_TYPE": "stdio",
131
+ "MCP_LOG_LEVEL": "info",
132
+ "EIA_API_KEY": "your-api-key"
133
+ }
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ Or with Docker:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "eia": {
145
+ "type": "stdio",
146
+ "command": "docker",
147
+ "args": [
148
+ "run", "-i", "--rm",
149
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
150
+ "-e", "EIA_API_KEY=your-api-key",
151
+ "ghcr.io/cyanheads/eia-energy-mcp-server:latest"
152
+ ]
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ For Streamable HTTP, set the transport and start the server:
159
+
160
+ ```sh
161
+ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 EIA_API_KEY=your-key bun run start:http
162
+ # Server listens at http://localhost:3010/mcp
163
+ ```
164
+
165
+ ### Prerequisites
166
+
167
+ - [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).
168
+ - A free EIA API key from [api.eia.gov](https://www.eia.gov/opendata/). The `DEMO_KEY` hits rate limits quickly; a real key is required for sustained use.
169
+
170
+ ### Installation
171
+
172
+ 1. **Clone the repository:**
173
+
174
+ ```sh
175
+ git clone https://github.com/cyanheads/eia-energy-mcp-server.git
176
+ ```
177
+
178
+ 2. **Navigate into the directory:**
179
+
180
+ ```sh
181
+ cd eia-energy-mcp-server
182
+ ```
183
+
184
+ 3. **Install dependencies:**
185
+
186
+ ```sh
187
+ bun install
188
+ ```
189
+
190
+ 4. **Configure environment:**
191
+
192
+ ```sh
193
+ cp .env.example .env
194
+ # edit .env and set required vars (at minimum, EIA_API_KEY)
195
+ ```
196
+
197
+ ## Configuration
198
+
199
+ All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
200
+
201
+ | Variable | Description | Default |
202
+ |:---------|:------------|:--------|
203
+ | `EIA_API_KEY` | **Required.** Free API key from api.eia.gov — appended as `api_key` on every request. | — |
204
+ | `EIA_BASE_URL` | EIA API base URL. | `https://api.eia.gov/v2` |
205
+ | `EIA_DATASET_TTL_SECONDS` | Per-table TTL for DataCanvas dataframes in seconds. | `86400` (24 h) |
206
+ | `EIA_DATAFRAME_DROP_ENABLED` | Set to `true` to expose `eia_dataframe_drop`. Off by default to avoid accidental canvas cleanup. | `false` |
207
+ | `CANVAS_PROVIDER_TYPE` | Set to `duckdb` to enable DataCanvas spillover for large result sets (Node only). | — |
208
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
209
+ | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
210
+ | `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path. | `/mcp` |
211
+ | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments. | — |
212
+ | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
213
+ | `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
214
+ | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
215
+ | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
216
+ | `OTEL_ENABLED` | Enable OpenTelemetry instrumentation. | `false` |
217
+
218
+ ## Running the server
219
+
220
+ ### Local development
221
+
222
+ - **Build and run:**
223
+
224
+ ```sh
225
+ # One-time build
226
+ bun run rebuild
227
+
228
+ # Run the built server
229
+ bun run start:stdio
230
+ # or
231
+ bun run start:http
232
+ ```
233
+
234
+ - **Run checks and tests:**
235
+
236
+ ```sh
237
+ bun run devcheck # Lint, format, typecheck, security
238
+ bun run test # Vitest test suite
239
+ bun run lint:mcp # Validate MCP definitions against spec
240
+ ```
241
+
242
+ ## Project structure
243
+
244
+ | Directory | Purpose |
245
+ |:----------|:--------|
246
+ | `src/index.ts` | `createApp()` entry point — registers tools and inits services. |
247
+ | `src/config` | Server-specific environment variable parsing and validation with Zod. |
248
+ | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) — browse, describe, search, query, and three DataCanvas dataframe tools. |
249
+ | `src/services/eia` | EIA API v2 service — route tree cache, Fuse.js index, facet fan-out, HTTP client. |
250
+ | `src/services/canvas-bridge` | DataCanvas bridge — registers EIA query results as DuckDB dataframes, routes SQL queries. |
251
+ | `tests/` | Unit and integration tests mirroring `src/`. |
252
+ | `docs/` | Design documents (`design.md`, `idea.md`). |
253
+
254
+ ## Development guide
255
+
256
+ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
257
+
258
+ - Handlers throw, framework catches — no `try/catch` in tool logic
259
+ - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage
260
+ - Always call `eia_describe_route` before `eia_query_route` — facet values require a separate API fan-out and are not embedded in route metadata
261
+ - Wrap EIA responses: validate raw → normalize to domain type → return output schema; data values are strings — never coerce silently
262
+
263
+ ## Contributing
264
+
265
+ Issues and pull requests are welcome. Run checks and tests before submitting:
266
+
267
+ ```sh
268
+ bun run devcheck
269
+ bun run test
270
+ ```
271
+
272
+ ## License
273
+
274
+ Apache-2.0 — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Initial scaffold from @cyanheads/mcp-ts-core with tool-surface design for the EIA API v2."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.0 — 2026-05-21
8
+
9
+ ## Added
10
+
11
+ - Project scaffolded from `@cyanheads/mcp-ts-core`.
12
+ - Idea seed (`docs/idea.md`) covering the EIA data domain: electricity, petroleum, natural gas, coal, international, STEO forecasts, and emissions.
13
+ - Tool-surface design (`docs/design.md`) for four tools: `eia_browse_routes`, `eia_describe_route`, `eia_search_routes`, `eia_query_route` — verified against the live EIA API v2 on 2026-05-21.
14
+ - Skills synced to `.claude/skills/`.
15
+
16
+ ## Removed
17
+
18
+ - Unused echo definitions (resource, app-resource, prompt, app-tool) — design is tools-only; no resources or prompts surface.
@@ -0,0 +1,42 @@
1
+ ---
2
+ summary: "Full tool surface implementation: EIA API service layer, four domain tools, three DataCanvas dataframe tools, and a complete test suite."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.1 — 2026-05-21
8
+
9
+ ## Added
10
+
11
+ - `src/config/server-config.ts` — Zod-validated env config for `EIA_API_KEY`, `EIA_BASE_URL`, `EIA_DATASET_TTL_SECONDS`, and `EIA_DATAFRAME_DROP_ENABLED`.
12
+ - `src/services/eia/types.ts` — EIA API v2 domain types: raw route/facet/data shapes and normalized domain objects.
13
+ - `src/services/eia/route-cache.ts` — In-process route tree cache with Fuse.js fuzzy index; STEO's 1,469 series names included. Built lazily on first browse/search call.
14
+ - `src/services/eia/eia-service.ts` — EIA API v2 service: route tree fetch, facet fan-out via `Promise.all`, data query with retry/rate-limit handling, and `OVER_RATE_LIMIT` → `ServiceUnavailable` classification.
15
+ - `src/services/canvas-bridge/canvas-bridge.ts` — DataCanvas bridge: registers query result sets as DuckDB dataframes, manages per-table TTL cleanup, graceful degradation when canvas is absent.
16
+ - `src/services/canvas-bridge/sql-gate-extras.ts` — SQL gate helpers for SELECT-only enforcement on dataframe queries.
17
+ - `src/mcp-server/tools/definitions/browse-routes.tool.ts` — `eia_browse_routes`: walk the EIA taxonomy from root to leaf.
18
+ - `src/mcp-server/tools/definitions/describe-route.tool.ts` — `eia_describe_route`: full leaf metadata with facet values, columns, frequencies, and date range.
19
+ - `src/mcp-server/tools/definitions/search-routes.tool.ts` — `eia_search_routes`: Fuse.js fuzzy search across route names, descriptions, and STEO series names.
20
+ - `src/mcp-server/tools/definitions/query-route.tool.ts` — `eia_query_route`: paginated data fetch with facet filters, date range, frequency, and DataCanvas spillover for large result sets.
21
+ - `src/mcp-server/tools/definitions/dataframe-describe.tool.ts` — `eia_dataframe_describe`: list active DataCanvas dataframes from prior query calls.
22
+ - `src/mcp-server/tools/definitions/dataframe-query.tool.ts` — `eia_dataframe_query`: read-only SQL SELECT against a DataCanvas dataframe by canvas ID or table name.
23
+ - `src/mcp-server/tools/definitions/dataframe-drop.tool.ts` — `eia_dataframe_drop`: drop a DataCanvas dataframe; exposed only when `EIA_DATAFRAME_DROP_ENABLED=true` (disabled by default via `disabledTool`).
24
+ - `tests/services/route-cache.test.ts` — Unit tests for the route-cache Fuse.js index and cache behavior.
25
+ - `tests/tools/browse-routes.tool.test.ts` — Tests for `eia_browse_routes` (root, intermediate, leaf paths, not-found error).
26
+ - `tests/tools/describe-route.tool.test.ts` — Tests for `eia_describe_route` (full metadata, sparse upstream, non-leaf rejection).
27
+ - `tests/tools/search-routes.tool.test.ts` — Tests for `eia_search_routes` (match, no-match, STEO series hit).
28
+ - `tests/tools/query-route.tool.test.ts` — Tests for `eia_query_route` (inline data, canvas spillover, rate-limit error, pagination).
29
+ - `tests/tools/dataframe-describe.tool.test.ts` — Tests for `eia_dataframe_describe`.
30
+ - `tests/tools/dataframe-query.tool.test.ts` — Tests for `eia_dataframe_query` (SELECT enforcement, unknown frame error).
31
+ - `tests/tools/dataframe-drop.tool.test.ts` — Tests for `eia_dataframe_drop` (success, unknown frame error).
32
+ - `fuse.js ^7.3.0` dependency for in-process fuzzy route search.
33
+
34
+ ## Changed
35
+
36
+ - `src/index.ts` — Replaced echo tool scaffolding with full tool registration (`browseRoutesTool`, `describeRouteTool`, `searchRoutesTool`, `queryRouteTool`, `dataframeDescribeTool`, `dataframeQueryTool`, `dropTool`), `setup()` hook calling `initEiaApiService()` and `initCanvasBridge()`.
37
+ - `scripts/list-skills.ts` — Updated skill listing script.
38
+
39
+ ## Removed
40
+
41
+ - `src/mcp-server/tools/definitions/echo.tool.ts` — Placeholder echo tool replaced by domain tools.
42
+ - `tests/tools/echo.tool.test.ts` — Echo tool tests removed with the tool.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.5, error code semantics for domain validation, MCPB bundle support."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.2 — 2026-05-23
8
+
9
+ ## Added
10
+
11
+ - **MCPB bundle support** — `manifest.json`, `.mcpbignore`, and `bundle` / `lint:packaging` / `audit:refresh` / `publish-mcp` scripts added; `bun run bundle` produces a `.mcpb` for one-click Claude Desktop install.
12
+
13
+ ## Changed
14
+
15
+ - **`@cyanheads/mcp-ts-core`** `^0.9.1 → ^0.9.5` — picks up `RequestContextLike` interface (0.9.3), error code refinements, and associated fixes.
16
+ - **`zod`** `^4.4.3` added as an explicit dependency (was previously a transitive-only dep).
17
+ - **`ctx as unknown as …` cast removed** from canvas bridge — `Context` is now directly assignable to `RequestContextLike` since 0.9.3; cast was dead code.
18
+ - **`async` removed** from `browse-routes` handler — handler body has no `await`; spurious modifier dropped.
19
+ - **Error code semantics** — domain validation errors in `eia_query_route` (facet validation, length checks) now throw `validationError()` instead of `invalidParams()`; aligns with framework contract for user-supplied value failures.
20
+ - **`describe-on-fields` lint fixes** across `browse-routes`, `describe-route`, `search-routes`, `query-route`, and `dataframe-query` tool files — all Zod fields now carry `.describe()`.
21
+ - **Skills synced** — `field-test` 2.4 → 2.5, `maintenance` 2.1 → 2.4, `polish-docs-meta` 1.8 → 2.1, `release-and-publish` 2.2 → 2.4.
22
+ - **`devcheck.ts` updated**, `lint-packaging.ts` added to `scripts/`.
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.5 → ^0.9.6, LICENSE file, lint-packaging updates."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.3 — 2026-05-23
8
+
9
+ ## Added
10
+
11
+ - **LICENSE** — Apache-2.0 license file added to the repo root and included in the npm package.
12
+
13
+ ## Changed
14
+
15
+ - **`@cyanheads/mcp-ts-core`** `^0.9.5 → ^0.9.6`.
16
+ - **`scripts/lint-packaging.ts`** — updated to validate manifest name scope and `user_config` field alignment.
17
+ - **Skills synced** — `polish-docs-meta` 2.1 → 2.2, `release-and-publish` 2.4 → 2.5.
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "Field-test bug fixes: route tree misclassification, ZodError on value-array columns, 4xx error codes, auto-populate data[] columns, STEO filter_hint, description normalization."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.4 — 2026-05-23
8
+
9
+ ## Fixed
10
+
11
+ - **Route tree classification** — second-level nodes were misclassified as leaves because `buildRouteTree` fetched them using only their segment `id` instead of the full path (e.g. `petroleum/gnd` instead of `gnd`). Now tracks `parentPath` through recursion and preserves the stub's `id`/`name` on merge so EIA's domain-category responses don't overwrite route segment IDs.
12
+ - **ZodError on `data.value:[]` column format** — time-series routes that return `{ value: [] }` instead of the standard `{ colId: { alias, units } }` shape caused a Zod parse crash when normalizing data columns. `describeRoute` now filters out array entries and synthesizes a `{ id: "value", alias: "Value", units: "" }` column instead.
13
+ - **EIA 4xx errors mapped to correct codes** — `404` responses now throw `NotFound` and `400` responses throw `ValidationError` (both non-retryable), instead of `ServiceUnavailable`. The upstream error body (`{ error: string }`) is preserved in the error message.
14
+ - **Auto-populate `data[]` columns** — `queryRoute` omitted the `data[]` query parameter when `columns` was not specified, causing EIA to return rows without measurement values. The call now falls back to all `dataColumns` from route metadata cache when `columns` is omitted.
15
+ - **STEO `filter_hint`** — `eia_search_routes` results for STEO series now include a `filter_hint` field (`{ seriesId: "<id>" }`) that can be passed directly as `filters` to `eia_query_route`, removing the need to parse the series ID from the description string.
16
+ - **Route description normalization** — EIA descriptions containing embedded `\r\n` + whitespace (source-level line wrapping) are now collapsed to clean single-line strings in both the route cache and `describeRoute` output.
17
+ - **`filters` vs `facets` cross-reference** — `eia_describe_route` facet `id` field description and `eia_query_route` `filters` parameter description now cross-reference each other by name (e.g. "use as key in the `filters` parameter of `eia_query_route`").
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "Pre-launch polish: code simplification, docs/metadata sync, bunfig.toml, Dockerfile labels, server.json env var coverage."
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.1.5 — 2026-05-23
7
+
8
+ ## Changed
9
+
10
+ - **`eia-service.ts`** — simplified `OVER_RATE_LIMIT` detection (extracted `parsedResponse` variable) and `dataColumns` map (extracted `col` binding); equivalent behavior.
11
+ - **`canvas-bridge.ts`** — moved `registerAs` destructure after the `instance.query()` call to match its actual first use.
12
+ - **`query-route.tool.ts`** — added `.describe()` to the passthrough row object in the output schema.
13
+ - **`server.json`** — added `EIA_DATASET_TTL_SECONDS`, `EIA_DATAFRAME_DROP_ENABLED`, and `CANVAS_PROVIDER_TYPE` env var entries to both stdio and HTTP packages (were missing from the registry manifest).
14
+ - **`Dockerfile`** — filled in `org.opencontainers.image.description` and added `org.opencontainers.image.source` label.
15
+ - **`.env.example`** — replaced generic placeholder section with concrete EIA and Canvas env var entries.
16
+ - **`README.md`** — added Docker badge, updated Cursor/VS Code deeplink configs to include `EIA_API_KEY` env, added `cp .env.example .env` install step, added `EIA_DATASET_TTL_SECONDS` and `EIA_DATAFRAME_DROP_ENABLED` to the configuration table.
17
+ - **`package.json`** — removed stale `AGENTS.md` from `files[]`; added `energy-api` and `llm` keywords.
18
+ - **`bunfig.toml`** — added with `[install] auto=fallback` and `[run] bun=true`.
19
+ - **`CLAUDE.md`** — updated server config pattern to reflect current `datasetTtlSeconds`/`dataframeDropEnabled` schema (removed stale `canvasProvider` field).
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "Field-test bug fixes: error contracts, schema handling, and UX across eia_describe_route, eia_query_route, and eia_search_routes."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.6 — 2026-05-23
8
+
9
+ ## Fixed
10
+
11
+ - **`eia_describe_route` / `eia_query_route`**: `route_not_found` error now fires with contract `reason` and recovery hint when the route does not exist.
12
+ - **`eia_query_route`**: `invalid_facet` error now fires with contract `reason` and recovery hint on a 400 from EIA; dead `invalid_facet_value` contract entry removed.
13
+ - **`eia_query_route`**: units columns extracted to table headers (`col (unit)`) instead of repeated as `{col}-units` fields on every row.
14
+ - **`eia_search_routes`**: description example replaced with one that resolves (`electricity retail sales by state`); weak-match label added for scores > 0.5.
15
+ - **`eia_describe_route`**: null facet `id`/`name` values from EIA (e.g. international route) are now filtered out instead of surfaced.
16
+ - **`eia_describe_route`**: undefined data column `alias` (e.g. crude-oil-imports route) now falls back to the column `id` instead of crashing.
17
+ - **`eia_query_route`**: category (non-leaf) route now returns a typed `ValidationError` before hitting the EIA API.
18
+ - **`eia_query_route`**: inverted date range (`start > end`) now returns a `no_data` validation error with a swap hint.
19
+ - **`eia_describe_route`**: nonexistent route now returns `route_not_found` with a recovery hint pointing to `eia_browse_routes` / `eia_search_routes`.
@@ -0,0 +1,11 @@
1
+ ---
2
+ summary: "Add @duckdb/node-api ^1.5.3-r.1 — enables DuckDB canvas provider for dataframe tools."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.7 — 2026-05-23
8
+
9
+ ## Dependencies
10
+
11
+ - `@duckdb/node-api ^1.5.3-r.1` added — provides the DuckDB runtime required by the DataCanvas canvas provider (`CANVAS_PROVIDER_TYPE=duckdb`). The three dataframe tools (`eia_dataframe_describe`, `eia_dataframe_query`, `eia_dataframe_drop`) now function end-to-end when DuckDB is enabled.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "Repo and package renamed from eia-mcp-server to eia-energy-mcp-server; tool names (eia_*) unchanged."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.2.0 — 2026-05-24
8
+
9
+ Package rename. All tool names (`eia_*`) and the MCP tool surface are unchanged — this is a breaking change only for npm consumers referencing the old package identifier.
10
+
11
+ ## Changed
12
+
13
+ - **npm package** renamed from `@cyanheads/eia-mcp-server` to `@cyanheads/eia-energy-mcp-server` ([#18](https://github.com/cyanheads/eia-energy-mcp-server/issues/18)).
14
+ - **GitHub repo** renamed from `cyanheads/eia-mcp-server` to `cyanheads/eia-energy-mcp-server`.
15
+ - **Docker image** renamed from `ghcr.io/cyanheads/eia-mcp-server` to `ghcr.io/cyanheads/eia-energy-mcp-server`.
16
+ - **Hosted endpoint** moving to `https://eia-energy.caseyjhand.com/mcp` (subdomain change; old `eia.caseyjhand.com` redirects during transition).
17
+ - **MCP server name** updated to `io.github.cyanheads/eia-energy-mcp-server` in `server.json`.
18
+ - **Tool names unchanged** — all seven `eia_*` tools retain their existing identifiers; no client-side tool renames needed.
19
+
20
+ ## Deprecated
21
+
22
+ - `@cyanheads/eia-mcp-server` on npm — deprecated with a migration notice pointing to `@cyanheads/eia-energy-mcp-server`.
@@ -0,0 +1,93 @@
1
+ ---
2
+ # FORMAT REFERENCE — do not edit. Copy this file to
3
+ # `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.8.x/0.8.6.md`)
4
+ # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
+ # with a concrete date.
6
+
7
+ # Required. One-line GitHub Release-style headline. 350 character cap.
8
+ # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
+ # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
+ # inside the value as a key separator and fails GitHub's strict parser.
11
+ summary: ""
12
+
13
+ # Set `true` when consumers must change code to upgrade: API removals,
14
+ # signature changes, config renames, behavior changes that break existing
15
+ # usage. Flagged as `Breaking` in the rollup.
16
+ breaking: false
17
+
18
+ # Set `true` if this release contains any security fix. Pairs with the
19
+ # `## Security` section below. Flagged as `Security` in the rollup so
20
+ # users can triage upgrade urgency at a glance.
21
+ security: false
22
+ ---
23
+
24
+ # <version> — YYYY-MM-DD
25
+
26
+ <!--
27
+ AUTHORING GUIDE — applies to the new per-version file you create from this
28
+ template.
29
+
30
+ Audience: someone scanning release notes to decide what affects them. Lead
31
+ each bullet with the symbol or concept name in **bold** so they can skip
32
+ what's irrelevant and zoom in on what's not.
33
+
34
+ Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
35
+ name the symbol, state what changed, stop. Use a second sentence only when
36
+ it carries weight. If a bullet feels long, it is.
37
+
38
+ Cut: mechanism walkthroughs (those belong in JSDoc, AGENTS.md, or the
39
+ relevant skill), ceremonial framings ("This release introduces…",
40
+ backwards-compat paragraphs), file-by-file test enumerations, internal
41
+ implementation notes. Prefer code/symbol names over English re-explanations.
42
+
43
+ Narrative intro: skip by default. Add one short sentence only when the
44
+ release theme genuinely needs framing the bullets can't carry.
45
+
46
+ Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
47
+ Fixed, Security. Include only sections with entries; delete the rest
48
+ (including the commented-out scaffolding below). Don't ship empty headers.
49
+
50
+ Include: every distinct fact a reader needs to adopt or audit the release —
51
+ new exports, signatures, lint rule IDs, env vars, breaking changes, version
52
+ bumps on shipped skills. Nothing more.
53
+
54
+ Links: link issues, PRs, docs, or skills where they help a reader jump to
55
+ context. Once per item per entry — don't re-link the same issue in summary,
56
+ narrative, and bullet. Skip links for inline symbol names; code spans speak
57
+ for themselves.
58
+
59
+ Issue/PR URLs: use full URLs. GitHub's bare `#NN` auto-link only resolves
60
+ inside its own UI, not in npm reads or local editors.
61
+
62
+ [#38](https://github.com/cyanheads/mcp-ts-core/issues/38) ← issue
63
+ [#42](https://github.com/cyanheads/mcp-ts-core/pull/42) ← PR
64
+
65
+ Verify numbers exist before linking (`gh issue view NN`, `gh pr view NN`).
66
+ Never speculate on a future number — `#42` for an upcoming PR silently
67
+ resolves to whatever real item already owns 42, and timeline previews pull
68
+ in that unrelated item's metadata.
69
+ -->
70
+
71
+ ## Added
72
+
73
+ -
74
+
75
+ ## Changed
76
+
77
+ -
78
+
79
+ <!-- ## Deprecated
80
+
81
+ - -->
82
+
83
+ <!-- ## Removed
84
+
85
+ - -->
86
+
87
+ ## Fixed
88
+
89
+ -
90
+
91
+ <!-- ## Security
92
+
93
+ - -->
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview EIA server-specific environment configuration. Parsed lazily on
3
+ * first call; validated via Zod so errors name the actual env var at fault.
4
+ * @module config/server-config
5
+ */
6
+ import { z } from '@cyanheads/mcp-ts-core';
7
+ declare const ServerConfigSchema: z.ZodObject<{
8
+ apiKey: z.ZodString;
9
+ baseUrl: z.ZodDefault<z.ZodString>;
10
+ datasetTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
11
+ dataframeDropEnabled: z.ZodDefault<z.ZodPreprocess<z.ZodBoolean>>;
12
+ }, z.core.$strip>;
13
+ export type ServerConfig = z.infer<typeof ServerConfigSchema>;
14
+ export declare function getServerConfig(): ServerConfig;
15
+ /** Reset for tests that need to change config. */
16
+ export declare function _resetServerConfig(): void;
17
+ export {};
18
+ //# sourceMappingURL=server-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;iBAatB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED,kDAAkD;AAClD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview EIA server-specific environment configuration. Parsed lazily on
3
+ * first call; validated via Zod so errors name the actual env var at fault.
4
+ * @module config/server-config
5
+ */
6
+ import { z } from '@cyanheads/mcp-ts-core';
7
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
8
+ const ServerConfigSchema = z.object({
9
+ apiKey: z.string().describe('EIA API key'),
10
+ baseUrl: z.string().url().default('https://api.eia.gov/v2').describe('EIA API base URL'),
11
+ datasetTtlSeconds: z.coerce
12
+ .number()
13
+ .int()
14
+ .positive()
15
+ .default(86400)
16
+ .describe('Per-table TTL for canvas dataframes in seconds (default 24 h)'),
17
+ dataframeDropEnabled: z
18
+ .preprocess((v) => v === 'true' || v === true, z.boolean())
19
+ .default(false)
20
+ .describe('Expose eia_dataframe_drop when true'),
21
+ });
22
+ let _config;
23
+ export function getServerConfig() {
24
+ _config ??= parseEnvConfig(ServerConfigSchema, {
25
+ apiKey: 'EIA_API_KEY',
26
+ baseUrl: 'EIA_BASE_URL',
27
+ datasetTtlSeconds: 'EIA_DATASET_TTL_SECONDS',
28
+ dataframeDropEnabled: 'EIA_DATAFRAME_DROP_ENABLED',
29
+ });
30
+ return _config;
31
+ }
32
+ /** Reset for tests that need to change config. */
33
+ export function _resetServerConfig() {
34
+ _config = undefined;
35
+ }
36
+ //# sourceMappingURL=server-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IACxF,iBAAiB,EAAE,CAAC,CAAC,MAAM;SACxB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,+DAA+D,CAAC;IAC5E,oBAAoB,EAAE,CAAC;SACpB,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC1D,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,qCAAqC,CAAC;CACnD,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,cAAc;QACvB,iBAAiB,EAAE,yBAAyB;QAC5C,oBAAoB,EAAE,4BAA4B;KACnD,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,kBAAkB;IAChC,OAAO,GAAG,SAAS,CAAC;AACtB,CAAC"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview eia-energy-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG"}