@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.
- package/CLAUDE.md +351 -0
- package/Dockerfile +99 -0
- package/LICENSE +195 -0
- package/README.md +274 -0
- package/changelog/0.1.x/0.1.0.md +18 -0
- package/changelog/0.1.x/0.1.1.md +42 -0
- package/changelog/0.1.x/0.1.2.md +22 -0
- package/changelog/0.1.x/0.1.3.md +17 -0
- package/changelog/0.1.x/0.1.4.md +17 -0
- package/changelog/0.1.x/0.1.5.md +19 -0
- package/changelog/0.1.x/0.1.6.md +19 -0
- package/changelog/0.1.x/0.1.7.md +11 -0
- package/changelog/0.2.x/0.2.0.md +22 -0
- package/changelog/template.md +93 -0
- package/dist/config/server-config.d.ts +18 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +36 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/browse-routes.tool.d.ts +28 -0
- package/dist/mcp-server/tools/definitions/browse-routes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/browse-routes.tool.js +72 -0
- package/dist/mcp-server/tools/definitions/browse-routes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-describe.tool.d.ts +34 -0
- package/dist/mcp-server/tools/definitions/dataframe-describe.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-describe.tool.js +114 -0
- package/dist/mcp-server/tools/definitions/dataframe-describe.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-drop.tool.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/dataframe-drop.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-drop.tool.js +56 -0
- package/dist/mcp-server/tools/definitions/dataframe-drop.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts +28 -0
- package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/dataframe-query.tool.js +124 -0
- package/dist/mcp-server/tools/definitions/dataframe-query.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/describe-route.tool.d.ts +58 -0
- package/dist/mcp-server/tools/definitions/describe-route.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/describe-route.tool.js +164 -0
- package/dist/mcp-server/tools/definitions/describe-route.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/query-route.tool.d.ts +66 -0
- package/dist/mcp-server/tools/definitions/query-route.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/query-route.tool.js +264 -0
- package/dist/mcp-server/tools/definitions/query-route.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-routes.tool.d.ts +23 -0
- package/dist/mcp-server/tools/definitions/search-routes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-routes.tool.js +94 -0
- package/dist/mcp-server/tools/definitions/search-routes.tool.js.map +1 -0
- package/dist/services/canvas-bridge/canvas-bridge.d.ts +68 -0
- package/dist/services/canvas-bridge/canvas-bridge.d.ts.map +1 -0
- package/dist/services/canvas-bridge/canvas-bridge.js +206 -0
- package/dist/services/canvas-bridge/canvas-bridge.js.map +1 -0
- package/dist/services/canvas-bridge/sql-gate-extras.d.ts +13 -0
- package/dist/services/canvas-bridge/sql-gate-extras.d.ts.map +1 -0
- package/dist/services/canvas-bridge/sql-gate-extras.js +37 -0
- package/dist/services/canvas-bridge/sql-gate-extras.js.map +1 -0
- package/dist/services/eia/eia-service.d.ts +72 -0
- package/dist/services/eia/eia-service.d.ts.map +1 -0
- package/dist/services/eia/eia-service.js +497 -0
- package/dist/services/eia/eia-service.js.map +1 -0
- package/dist/services/eia/route-cache.d.ts +65 -0
- package/dist/services/eia/route-cache.d.ts.map +1 -0
- package/dist/services/eia/route-cache.js +168 -0
- package/dist/services/eia/route-cache.js.map +1 -0
- package/dist/services/eia/types.d.ts +115 -0
- package/dist/services/eia/types.d.ts.map +1 -0
- package/dist/services/eia/types.js +7 -0
- package/dist/services/eia/types.js.map +1 -0
- package/package.json +104 -0
- package/server.json +163 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview EIA API v2 service. Wraps api.eia.gov/v2 with retry/timeout,
|
|
3
|
+
* route tree caching, per-route facet metadata caching, and Fuse.js fuzzy
|
|
4
|
+
* search. Exposes browse, describe, query, and search methods consumed by MCP
|
|
5
|
+
* tool handlers. Rate-limit detection: EIA returns `OVER_RATE_LIMIT` in the
|
|
6
|
+
* response body — classified as ServiceUnavailable (retryable).
|
|
7
|
+
* @module services/eia/eia-service
|
|
8
|
+
*/
|
|
9
|
+
import { McpError, notFound, serviceUnavailable, validationError, } from '@cyanheads/mcp-ts-core/errors';
|
|
10
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
11
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
12
|
+
import { addSteoSeriesToIndex, getChildren, getIndexSize, getNode, initRouteCache, isLeafNode, isRouteCacheReady, normalizeDescription, searchRoutes, } from './route-cache.js';
|
|
13
|
+
/** Per-route merged metadata cache (populated by describe). */
|
|
14
|
+
const _routeMetaCache = new Map();
|
|
15
|
+
/** Pending initialization promise (prevents duplicate warm-up). */
|
|
16
|
+
let _initPromise;
|
|
17
|
+
class EiaApiService {
|
|
18
|
+
get baseUrl() {
|
|
19
|
+
return getServerConfig().baseUrl;
|
|
20
|
+
}
|
|
21
|
+
get apiKey() {
|
|
22
|
+
return getServerConfig().apiKey;
|
|
23
|
+
}
|
|
24
|
+
buildUrl(path, params = {}) {
|
|
25
|
+
const url = new URL(`${this.baseUrl}/${path}`);
|
|
26
|
+
url.searchParams.set('api_key', this.apiKey);
|
|
27
|
+
for (const [key, value] of Object.entries(params)) {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
for (const v of value)
|
|
30
|
+
url.searchParams.append(key, v);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
url.searchParams.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
fetchJson(path, params = {}, ctx) {
|
|
39
|
+
const url = this.buildUrl(path, params);
|
|
40
|
+
return withRetry(async () => {
|
|
41
|
+
const response = await fetch(url, { signal: ctx.signal });
|
|
42
|
+
const text = await response.text();
|
|
43
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
44
|
+
throw serviceUnavailable('EIA API returned HTML — likely rate-limited or unavailable.', {
|
|
45
|
+
reason: 'rate_limited',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
if (response.status === 429 || text.includes('OVER_RATE_LIMIT')) {
|
|
50
|
+
throw serviceUnavailable('EIA rate limit exceeded.', {
|
|
51
|
+
reason: 'rate_limited',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Parse EIA's error body — it often includes an actionable message.
|
|
55
|
+
// Shape: { error: string, code: number } or plain text.
|
|
56
|
+
let upstreamMessage;
|
|
57
|
+
try {
|
|
58
|
+
const errBody = JSON.parse(text);
|
|
59
|
+
if (typeof errBody.error === 'string')
|
|
60
|
+
upstreamMessage = errBody.error;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// non-JSON body — ignore
|
|
64
|
+
}
|
|
65
|
+
const detail = upstreamMessage
|
|
66
|
+
? `EIA API error: ${upstreamMessage}`
|
|
67
|
+
: `EIA API returned HTTP ${response.status}.`;
|
|
68
|
+
if (response.status === 404) {
|
|
69
|
+
// 404s are definitive — NotFound code is not transient, withRetry won't retry
|
|
70
|
+
throw notFound(detail, { status: response.status });
|
|
71
|
+
}
|
|
72
|
+
if (response.status === 400) {
|
|
73
|
+
// 400s are definitive — ValidationError code is not transient, withRetry won't retry
|
|
74
|
+
throw validationError(detail, { status: response.status });
|
|
75
|
+
}
|
|
76
|
+
// 5xx and other status codes — transient, eligible for retry
|
|
77
|
+
throw serviceUnavailable(detail, { status: response.status });
|
|
78
|
+
}
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(text);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw serviceUnavailable('EIA API returned non-JSON response.', {
|
|
85
|
+
reason: 'rate_limited',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const parsedResponse = typeof parsed === 'object' && parsed !== null && 'response' in parsed
|
|
89
|
+
? parsed.response
|
|
90
|
+
: undefined;
|
|
91
|
+
if (typeof parsedResponse === 'string' && parsedResponse.includes('OVER_RATE_LIMIT')) {
|
|
92
|
+
throw serviceUnavailable('EIA rate limit exceeded (OVER_RATE_LIMIT).', {
|
|
93
|
+
reason: 'rate_limited',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
}, {
|
|
98
|
+
operation: 'EiaApiService.fetchJson',
|
|
99
|
+
baseDelayMs: 1000,
|
|
100
|
+
signal: ctx.signal,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Warm the route tree cache. Called lazily on first browse/search.
|
|
105
|
+
* Fetches top-level routes, recursively discovers children via the cache
|
|
106
|
+
* structure from GET /v2/ and per-node GETs, then builds the Fuse.js index.
|
|
107
|
+
* STEO series are fetched in parallel and appended to the index.
|
|
108
|
+
*/
|
|
109
|
+
async ensureCacheWarmed(ctx) {
|
|
110
|
+
if (isRouteCacheReady())
|
|
111
|
+
return;
|
|
112
|
+
if (_initPromise) {
|
|
113
|
+
await _initPromise;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
_initPromise = this.warmCache(ctx);
|
|
117
|
+
try {
|
|
118
|
+
await _initPromise;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
_initPromise = undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async warmCache(ctx) {
|
|
125
|
+
ctx.log.info('Warming EIA route tree cache');
|
|
126
|
+
// Fetch root to get top-level route IDs
|
|
127
|
+
const rootResponse = await this.fetchJson('', {}, ctx);
|
|
128
|
+
const topLevelNodes = rootResponse?.response?.routes ?? [];
|
|
129
|
+
if (topLevelNodes.length === 0) {
|
|
130
|
+
throw serviceUnavailable('EIA root endpoint returned no routes.');
|
|
131
|
+
}
|
|
132
|
+
// For each top-level node, fetch its metadata to discover sub-routes and
|
|
133
|
+
// leaf status. We do this recursively until we hit leaf nodes.
|
|
134
|
+
// Strategy: fetch each top-level node and recurse into non-leaf children.
|
|
135
|
+
const fullTree = await this.buildRouteTree(topLevelNodes, ctx);
|
|
136
|
+
// Build route map and index (without STEO series initially)
|
|
137
|
+
initRouteCache(fullTree, []);
|
|
138
|
+
// Fetch STEO series in the background to populate the index
|
|
139
|
+
this.fetchSteoSeries(ctx).catch((err) => {
|
|
140
|
+
ctx.log.warning('Failed to index STEO series', {
|
|
141
|
+
error: err instanceof Error ? err.message : String(err),
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
ctx.log.info('EIA route tree cache warmed', { indexSize: getIndexSize() });
|
|
145
|
+
}
|
|
146
|
+
async buildRouteTree(nodes, ctx, depth = 0, parentPath = '') {
|
|
147
|
+
// Limit recursion depth to avoid excessive API calls
|
|
148
|
+
if (depth > 5)
|
|
149
|
+
return nodes;
|
|
150
|
+
const enriched = await Promise.all(nodes.map(async (node) => {
|
|
151
|
+
const nodePath = parentPath ? `${parentPath}/${node.id}` : node.id;
|
|
152
|
+
// If the node already has sub-routes or leaf indicators, use as-is
|
|
153
|
+
if (node.routes?.length || node.frequency || node.facets || node.data) {
|
|
154
|
+
if (node.routes?.length) {
|
|
155
|
+
const children = await this.buildRouteTree(node.routes, ctx, depth + 1, nodePath);
|
|
156
|
+
return { ...node, routes: children };
|
|
157
|
+
}
|
|
158
|
+
return node;
|
|
159
|
+
}
|
|
160
|
+
// Otherwise fetch the node's metadata using its full path.
|
|
161
|
+
// Preserve node.id — EIA leaf responses return the domain category in
|
|
162
|
+
// their top-level `id` field (e.g. "petroleum"), not the route segment
|
|
163
|
+
// (e.g. "gnd"). Without this guard the merge would overwrite the
|
|
164
|
+
// segment ID and corrupt the path when buildNodeMap runs later.
|
|
165
|
+
try {
|
|
166
|
+
const resp = await this.fetchJson(nodePath, {}, ctx);
|
|
167
|
+
const fetched = resp?.response;
|
|
168
|
+
if (!fetched)
|
|
169
|
+
return node;
|
|
170
|
+
// Preserve id and name from the stub — EIA leaf responses use the
|
|
171
|
+
// domain category as `id` (not the route segment) and often omit `name`.
|
|
172
|
+
const merged = { ...fetched, id: node.id, name: node.name ?? fetched.id };
|
|
173
|
+
if (merged.routes?.length) {
|
|
174
|
+
const children = await this.buildRouteTree(merged.routes, ctx, depth + 1, nodePath);
|
|
175
|
+
return { ...merged, routes: children };
|
|
176
|
+
}
|
|
177
|
+
return merged;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return node;
|
|
181
|
+
}
|
|
182
|
+
}));
|
|
183
|
+
return enriched;
|
|
184
|
+
}
|
|
185
|
+
async fetchSteoSeries(ctx) {
|
|
186
|
+
// Fetch STEO series via facet endpoint
|
|
187
|
+
const resp = await this.fetchJson('steo/facet/seriesId', {}, ctx);
|
|
188
|
+
const facets = resp?.response?.facets ?? [];
|
|
189
|
+
if (facets.length === 0)
|
|
190
|
+
return;
|
|
191
|
+
const entries = facets.map((f) => ({
|
|
192
|
+
route: 'steo',
|
|
193
|
+
name: f.name,
|
|
194
|
+
description: `STEO series: ${f.name} (${f.id})${f.alias ? ` — ${f.alias}` : ''}`,
|
|
195
|
+
isLeaf: true,
|
|
196
|
+
category: 'steo',
|
|
197
|
+
filter_hint: { seriesId: f.id },
|
|
198
|
+
}));
|
|
199
|
+
addSteoSeriesToIndex(entries);
|
|
200
|
+
ctx.log.info('STEO series indexed', { count: entries.length });
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Browse child routes at a given path. Returns list of children with leaf
|
|
204
|
+
* classification.
|
|
205
|
+
*/
|
|
206
|
+
async browse(path, ctx) {
|
|
207
|
+
await this.ensureCacheWarmed(ctx);
|
|
208
|
+
const normalizedPath = path?.trim() ?? '';
|
|
209
|
+
// Check if the path itself is a leaf
|
|
210
|
+
if (normalizedPath) {
|
|
211
|
+
const node = getNode(normalizedPath);
|
|
212
|
+
if (!node) {
|
|
213
|
+
throw notFound(`Route "${normalizedPath}" not found in the EIA taxonomy.`, {
|
|
214
|
+
reason: 'route_not_found',
|
|
215
|
+
recovery: {
|
|
216
|
+
hint: 'Call eia_browse_routes without a path to see valid top-level categories.',
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const selfIsLeaf = isLeafNode(node);
|
|
221
|
+
if (selfIsLeaf) {
|
|
222
|
+
return { path: normalizedPath, children: [], isLeaf: true };
|
|
223
|
+
}
|
|
224
|
+
const childEntries = getChildren(normalizedPath);
|
|
225
|
+
const children = childEntries.map(({ id, route, node: childNode }) => ({
|
|
226
|
+
id,
|
|
227
|
+
name: childNode.name,
|
|
228
|
+
description: childNode.description ?? '',
|
|
229
|
+
route,
|
|
230
|
+
isLeaf: isLeafNode(childNode),
|
|
231
|
+
}));
|
|
232
|
+
return { path: normalizedPath, children, isLeaf: false };
|
|
233
|
+
}
|
|
234
|
+
// Root browse
|
|
235
|
+
const rootChildren = getChildren('');
|
|
236
|
+
const children = rootChildren.map(({ id, route, node }) => ({
|
|
237
|
+
id,
|
|
238
|
+
name: node.name,
|
|
239
|
+
description: node.description ?? '',
|
|
240
|
+
route,
|
|
241
|
+
isLeaf: isLeafNode(node),
|
|
242
|
+
}));
|
|
243
|
+
return { path: '', children, isLeaf: false };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Describe a leaf route — returns full metadata including facets with values.
|
|
247
|
+
* Results are cached per-route to avoid repeat fan-out.
|
|
248
|
+
*/
|
|
249
|
+
async describe(route, ctx) {
|
|
250
|
+
const cached = _routeMetaCache.get(route);
|
|
251
|
+
if (cached)
|
|
252
|
+
return cached;
|
|
253
|
+
await this.ensureCacheWarmed(ctx);
|
|
254
|
+
const node = route ? getNode(route) : undefined;
|
|
255
|
+
if (!node && route) {
|
|
256
|
+
// Try fetching directly from EIA in case route tree walk missed it
|
|
257
|
+
await this.fetchAndCacheMetadata(route, ctx);
|
|
258
|
+
const meta = _routeMetaCache.get(route);
|
|
259
|
+
if (!meta) {
|
|
260
|
+
throw notFound(`Route "${route}" not found in the EIA taxonomy.`, {
|
|
261
|
+
reason: 'route_not_found',
|
|
262
|
+
recovery: {
|
|
263
|
+
hint: 'Use eia_browse_routes or eia_search_routes to discover valid route paths.',
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return meta;
|
|
268
|
+
}
|
|
269
|
+
if (node && !isLeafNode(node)) {
|
|
270
|
+
throw validationError(`Route "${route}" is a category, not a leaf — it has no data to query.`, {
|
|
271
|
+
reason: 'route_not_queryable',
|
|
272
|
+
recovery: {
|
|
273
|
+
hint: 'Use eia_browse_routes to drill into sub-routes, or eia_search_routes to find leaf routes.',
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
await this.fetchAndCacheMetadata(route, ctx);
|
|
278
|
+
const meta = _routeMetaCache.get(route);
|
|
279
|
+
if (!meta) {
|
|
280
|
+
throw notFound(`Could not retrieve metadata for route "${route}".`, {
|
|
281
|
+
reason: 'route_not_found',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return meta;
|
|
285
|
+
}
|
|
286
|
+
async fetchAndCacheMetadata(route, ctx) {
|
|
287
|
+
// Fetch route metadata — remap 404 to a typed route_not_found error
|
|
288
|
+
let metaRespRaw;
|
|
289
|
+
try {
|
|
290
|
+
metaRespRaw = await this.fetchJson(`${route}`, {}, ctx);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
if (err instanceof McpError && err.code === -32001 /* NotFound */) {
|
|
294
|
+
throw notFound(`Route "${route}" not found in the EIA taxonomy.`, {
|
|
295
|
+
reason: 'route_not_found',
|
|
296
|
+
recovery: {
|
|
297
|
+
hint: 'Use eia_browse_routes or eia_search_routes to discover valid route paths.',
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
const rawNode = metaRespRaw?.response;
|
|
304
|
+
if (!rawNode) {
|
|
305
|
+
throw notFound(`Route "${route}" returned empty metadata.`, { reason: 'route_not_found' });
|
|
306
|
+
}
|
|
307
|
+
if (!isLeafNode(rawNode)) {
|
|
308
|
+
throw validationError(`Route "${route}" is a category, not a leaf.`, {
|
|
309
|
+
reason: 'route_not_queryable',
|
|
310
|
+
recovery: {
|
|
311
|
+
hint: 'Use eia_browse_routes to drill into sub-routes, or eia_search_routes to find leaf routes.',
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// Fan out facet value fetches
|
|
316
|
+
const facetMetas = rawNode.facets ?? [];
|
|
317
|
+
const facetResults = await Promise.all(facetMetas.map(async (f) => {
|
|
318
|
+
try {
|
|
319
|
+
const resp = await this.fetchJson(`${route}/facet/${f.id}`, {}, ctx);
|
|
320
|
+
const values = resp?.response?.facets ?? [];
|
|
321
|
+
return {
|
|
322
|
+
id: f.id,
|
|
323
|
+
description: f.description,
|
|
324
|
+
// Filter null id/name entries — EIA returns null for some facet
|
|
325
|
+
// values (e.g. international route), which carry no usable filter value.
|
|
326
|
+
values: values
|
|
327
|
+
.filter((v) => v.id != null && v.name != null)
|
|
328
|
+
.map((v) => ({
|
|
329
|
+
id: v.id,
|
|
330
|
+
name: v.name,
|
|
331
|
+
...(v.alias !== undefined && { alias: v.alias }),
|
|
332
|
+
})),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
// If a single facet fetch fails, return with empty values
|
|
337
|
+
return { id: f.id, description: f.description, values: [] };
|
|
338
|
+
}
|
|
339
|
+
}));
|
|
340
|
+
// Normalize data columns. EIA uses two data field shapes:
|
|
341
|
+
// Standard: { colId: { alias: string, units: string }, ... }
|
|
342
|
+
// Value-array: { value: [] } — time-series routes where the single data
|
|
343
|
+
// column is always named "value". Synthesize a minimal DataColumn entry
|
|
344
|
+
// so query() can auto-populate data[]=value and return actual measurements.
|
|
345
|
+
const dataObj = rawNode.data ?? {};
|
|
346
|
+
const dataColumns = Object.entries(dataObj)
|
|
347
|
+
.filter(([, meta]) => meta !== null && typeof meta === 'object' && !Array.isArray(meta))
|
|
348
|
+
.map(([id, meta]) => {
|
|
349
|
+
const col = meta;
|
|
350
|
+
// alias and units may be undefined for some EIA routes (e.g. crude-oil-imports)
|
|
351
|
+
return { id, alias: col.alias ?? id, units: col.units ?? '' };
|
|
352
|
+
});
|
|
353
|
+
// Handle the value-array variant: { value: [] }
|
|
354
|
+
if (dataColumns.length === 0 && 'value' in dataObj && Array.isArray(dataObj.value)) {
|
|
355
|
+
dataColumns.push({ id: 'value', alias: 'Value', units: '' });
|
|
356
|
+
}
|
|
357
|
+
const frequencies = rawNode.frequency ?? [];
|
|
358
|
+
const meta = {
|
|
359
|
+
route,
|
|
360
|
+
description: normalizeDescription(rawNode.description),
|
|
361
|
+
facets: facetResults,
|
|
362
|
+
dataColumns,
|
|
363
|
+
frequencies,
|
|
364
|
+
dateRange: {
|
|
365
|
+
start: rawNode.startPeriod ?? '',
|
|
366
|
+
end: rawNode.endPeriod ?? '',
|
|
367
|
+
},
|
|
368
|
+
defaultFrequency: rawNode.defaultFrequency ?? frequencies[0]?.id ?? '',
|
|
369
|
+
defaultDateFormat: rawNode.defaultDateFormat ?? '',
|
|
370
|
+
};
|
|
371
|
+
_routeMetaCache.set(route, meta);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Fetch data from a leaf route. Returns rows (all string values per EIA API),
|
|
375
|
+
* total count, and any warnings.
|
|
376
|
+
*/
|
|
377
|
+
async query(route, opts, ctx) {
|
|
378
|
+
if ((opts.length ?? 100) > 5000) {
|
|
379
|
+
throw validationError('length exceeds EIA maximum of 5000 rows per request.', {
|
|
380
|
+
reason: 'length_exceeded',
|
|
381
|
+
maxLength: 5000,
|
|
382
|
+
recovery: {
|
|
383
|
+
hint: 'Reduce length to 5000 or use offset pagination to retrieve more rows.',
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Pre-flight: if the route is in the cache as a category node, fail early
|
|
388
|
+
// with a typed error rather than letting the EIA API return a generic 404.
|
|
389
|
+
await this.ensureCacheWarmed(ctx);
|
|
390
|
+
const cachedNode = getNode(route);
|
|
391
|
+
if (cachedNode && !isLeafNode(cachedNode)) {
|
|
392
|
+
throw validationError(`Route "${route}" is a category, not a leaf — it has no data to query.`, {
|
|
393
|
+
reason: 'route_not_found',
|
|
394
|
+
recovery: {
|
|
395
|
+
hint: 'Use eia_browse_routes or eia_search_routes to find a valid leaf route path.',
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const params = {};
|
|
400
|
+
if (opts.frequency)
|
|
401
|
+
params.frequency = opts.frequency;
|
|
402
|
+
if (opts.start)
|
|
403
|
+
params.start = opts.start;
|
|
404
|
+
if (opts.end)
|
|
405
|
+
params.end = opts.end;
|
|
406
|
+
if (opts.offset !== undefined)
|
|
407
|
+
params.offset = String(opts.offset);
|
|
408
|
+
if (opts.length !== undefined)
|
|
409
|
+
params.length = String(opts.length);
|
|
410
|
+
// EIA only returns value fields when data[] params are explicitly set.
|
|
411
|
+
// When the caller omits columns, auto-populate from route metadata so
|
|
412
|
+
// all available data columns are included by default.
|
|
413
|
+
let columnsToRequest = opts.columns;
|
|
414
|
+
if (!columnsToRequest?.length) {
|
|
415
|
+
const cached = _routeMetaCache.get(route);
|
|
416
|
+
if (cached?.dataColumns.length) {
|
|
417
|
+
columnsToRequest = cached.dataColumns.map((c) => c.id);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (columnsToRequest?.length) {
|
|
421
|
+
params['data[]'] = columnsToRequest;
|
|
422
|
+
}
|
|
423
|
+
if (opts.filters) {
|
|
424
|
+
for (const [facetId, values] of Object.entries(opts.filters)) {
|
|
425
|
+
const arr = Array.isArray(values) ? values : [values];
|
|
426
|
+
params[`facets[${facetId}][]`] = arr;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const [i, s] of (opts.sort ?? []).entries()) {
|
|
430
|
+
params[`sort[${i}][column]`] = s.column;
|
|
431
|
+
params[`sort[${i}][direction]`] = s.direction;
|
|
432
|
+
}
|
|
433
|
+
let resp;
|
|
434
|
+
try {
|
|
435
|
+
resp = await this.fetchJson(`${route}/data/`, params, ctx);
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
if (err instanceof McpError) {
|
|
439
|
+
if (err.code === -32001 /* NotFound */) {
|
|
440
|
+
throw notFound(`Route "${route}" not found in the EIA taxonomy.`, {
|
|
441
|
+
reason: 'route_not_found',
|
|
442
|
+
recovery: {
|
|
443
|
+
hint: 'Use eia_browse_routes or eia_search_routes to find a valid leaf route path.',
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (err.code === -32007 /* ValidationError */) {
|
|
448
|
+
// 400 from EIA — likely an invalid facet key. Surface the EIA message
|
|
449
|
+
// plus the contract recovery hint.
|
|
450
|
+
const eiaMsg = err.message;
|
|
451
|
+
throw validationError(eiaMsg, {
|
|
452
|
+
reason: 'invalid_facet',
|
|
453
|
+
recovery: {
|
|
454
|
+
hint: 'Call eia_describe_route to see valid facet IDs for this route.',
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
461
|
+
const response = resp?.response;
|
|
462
|
+
if (!response) {
|
|
463
|
+
throw notFound(`Route "${route}" returned no data response.`, { reason: 'no_data' });
|
|
464
|
+
}
|
|
465
|
+
const total = parseInt(response.total ?? '0', 10);
|
|
466
|
+
const data = response.data ?? [];
|
|
467
|
+
return {
|
|
468
|
+
total,
|
|
469
|
+
dateFormat: response.dateFormat ?? '',
|
|
470
|
+
frequency: response.frequency ?? opts.frequency ?? '',
|
|
471
|
+
data,
|
|
472
|
+
warnings: response.warnings ?? undefined,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/** Fuzzy search across the route index. */
|
|
476
|
+
async search(query, limit, ctx) {
|
|
477
|
+
await this.ensureCacheWarmed(ctx);
|
|
478
|
+
const results = searchRoutes(query, limit);
|
|
479
|
+
return { results, totalIndexed: getIndexSize() };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
let _service;
|
|
483
|
+
export function initEiaApiService() {
|
|
484
|
+
_service = new EiaApiService();
|
|
485
|
+
}
|
|
486
|
+
export function getEiaApiService() {
|
|
487
|
+
if (!_service)
|
|
488
|
+
throw new Error('EiaApiService not initialized — call initEiaApiService() in setup()');
|
|
489
|
+
return _service;
|
|
490
|
+
}
|
|
491
|
+
/** Reset for tests. */
|
|
492
|
+
export function _resetEiaApiService() {
|
|
493
|
+
_service = undefined;
|
|
494
|
+
_routeMetaCache.clear();
|
|
495
|
+
_initPromise = undefined;
|
|
496
|
+
}
|
|
497
|
+
//# sourceMappingURL=eia-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eia-service.js","sourceRoot":"","sources":["../../../src/services/eia/eia-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview In-process route tree cache and Fuse.js fuzzy search index.
|
|
3
|
+
* The route tree is fetched lazily on first use and held for the process
|
|
4
|
+
* lifetime — EIA's taxonomy is stable between API releases and restarting the
|
|
5
|
+
* server is the appropriate refresh mechanism. The Fuse.js index is built once
|
|
6
|
+
* after the tree is populated and includes STEO series names so natural-language
|
|
7
|
+
* queries resolve to specific seriesId values.
|
|
8
|
+
* @module services/eia/route-cache
|
|
9
|
+
*/
|
|
10
|
+
import Fuse from 'fuse.js';
|
|
11
|
+
import type { RawRouteNode, SearchIndexEntry } from './types.js';
|
|
12
|
+
/** Holds the in-process route tree state. */
|
|
13
|
+
interface CacheState {
|
|
14
|
+
/** Sorted list of all index entries for total_indexed count. */
|
|
15
|
+
entries: SearchIndexEntry[];
|
|
16
|
+
/** Fuse.js index built over all routes + STEO series. */
|
|
17
|
+
fuseIndex: Fuse<SearchIndexEntry>;
|
|
18
|
+
/** Flat map of route path → raw node (all nodes in the tree). */
|
|
19
|
+
nodeMap: Map<string, RawRouteNode>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a description string from the EIA API. EIA descriptions often
|
|
23
|
+
* contain embedded `\r\n` + leading whitespace (source-level line wrapping).
|
|
24
|
+
* Collapse to a clean single-line string.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeDescription(desc: string | undefined): string;
|
|
27
|
+
/** Walk the raw route tree and collect all nodes into a flat path→node map. */
|
|
28
|
+
export declare function buildNodeMap(nodes: RawRouteNode[], parentPath: string, map: Map<string, RawRouteNode>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Classify a raw node as a leaf. A node is a leaf when it has `frequency`,
|
|
31
|
+
* `facets`, or `data` fields (queryable data endpoint) rather than a `routes`
|
|
32
|
+
* array. Root-level nodes with no sub-routes and no data fields are treated as
|
|
33
|
+
* leaves (e.g. steo).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isLeafNode(node: RawRouteNode): boolean;
|
|
36
|
+
/** Initialize the cache with the fetched route tree and optional STEO entries. */
|
|
37
|
+
export declare function initRouteCache(topLevelNodes: RawRouteNode[], steoSeriesEntries: SearchIndexEntry[]): void;
|
|
38
|
+
/** Return the cache, throwing if not yet initialized. */
|
|
39
|
+
export declare function getRouteCache(): CacheState;
|
|
40
|
+
/** True when the cache has been populated. */
|
|
41
|
+
export declare function isRouteCacheReady(): boolean;
|
|
42
|
+
/** Reset the cache (used in tests). */
|
|
43
|
+
export declare function _resetRouteCache(): void;
|
|
44
|
+
/** Get a node by route path. Returns undefined when not found. */
|
|
45
|
+
export declare function getNode(path: string): RawRouteNode | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Get children of a given path. For root (empty path), returns top-level nodes.
|
|
48
|
+
* Returns empty array when path has no children.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getChildren(path: string): Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
route: string;
|
|
53
|
+
node: RawRouteNode;
|
|
54
|
+
}>;
|
|
55
|
+
/** Fuzzy search across the index. Returns ranked matches. */
|
|
56
|
+
export declare function searchRoutes(query: string, limit: number): Array<{
|
|
57
|
+
entry: SearchIndexEntry;
|
|
58
|
+
score: number;
|
|
59
|
+
}>;
|
|
60
|
+
/** Total number of indexed entries. */
|
|
61
|
+
export declare function getIndexSize(): number;
|
|
62
|
+
/** Add STEO series entries to an already-initialized cache. */
|
|
63
|
+
export declare function addSteoSeriesToIndex(steoEntries: SearchIndexEntry[]): void;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=route-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-cache.d.ts","sourceRoot":"","sources":["../../../src/services/eia/route-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,IAA2B,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEjE,6CAA6C;AAC7C,UAAU,UAAU;IAClB,gEAAgE;IAChE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,yDAAyD;IACzD,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAClC,iEAAiE;IACjE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACpC;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOrE;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAC7B,IAAI,CAaN;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAMtD;AA+BD,kFAAkF;AAClF,wBAAgB,cAAc,CAC5B,aAAa,EAAE,YAAY,EAAE,EAC7B,iBAAiB,EAAE,gBAAgB,EAAE,GACpC,IAAI,CAYN;AAED,yDAAyD;AACzD,wBAAgB,aAAa,IAAI,UAAU,CAG1C;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAO9D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,GACX,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAuB1D;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAOnD;AAED,uCAAuC;AACvC,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAI1E"}
|