@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
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @fileoverview Tool definition for eia_describe_route. Returns full metadata
3
+ * for a leaf route: facets with valid values, data columns, frequencies, units,
4
+ * and date range. Required reading before constructing filters for eia_query_route.
5
+ * Facet values are fetched from separate /facet/{id} endpoints and merged.
6
+ * @module mcp-server/tools/definitions/describe-route.tool
7
+ */
8
+ import { tool, z } from '@cyanheads/mcp-ts-core';
9
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
10
+ import { getEiaApiService } from '../../../services/eia/eia-service.js';
11
+ export const describeRouteTool = tool('eia_describe_route', {
12
+ title: 'Describe EIA Route',
13
+ description: 'Returns full metadata for a leaf route: available facets with their valid values, data column names and units, frequency options, and date range. Call this before eia_query_route to discover valid facet IDs, facet values, column IDs, and frequency codes. Facet values are fetched from separate EIA endpoints and merged — results are cached per-route for the process lifetime to minimize API calls.',
14
+ annotations: { readOnlyHint: true, openWorldHint: false },
15
+ input: z.object({
16
+ route: z
17
+ .string()
18
+ .min(1)
19
+ .describe('Leaf route path (e.g. "electricity/retail-sales", "steo"). Discoverable via eia_browse_routes or eia_search_routes.'),
20
+ }),
21
+ output: z.object({
22
+ route: z.string().describe('The route path described.'),
23
+ description: z.string().describe('Human-readable description of the dataset.'),
24
+ facets: z
25
+ .array(z
26
+ .object({
27
+ id: z
28
+ .string()
29
+ .describe('Facet ID — use as key in the filters parameter of eia_query_route (e.g. filters: { "stateid": "TX" }).'),
30
+ description: z.string().describe('Facet description.'),
31
+ values: z
32
+ .array(z
33
+ .object({
34
+ id: z.string().describe('Facet value ID — use as filter value.'),
35
+ name: z.string().describe('Human-readable name.'),
36
+ alias: z.string().optional().describe('Short alias, when provided by EIA.'),
37
+ })
38
+ .describe('A valid facet value.'))
39
+ .describe('Valid values for this facet dimension.'),
40
+ })
41
+ .describe('A filterable dimension for this route.'))
42
+ .describe('Filterable dimensions. Each facet has an ID and a set of valid values.'),
43
+ data_columns: z
44
+ .array(z
45
+ .object({
46
+ id: z.string().describe('Column ID — use in the columns parameter of eia_query_route.'),
47
+ alias: z.string().describe('Human-readable column alias.'),
48
+ units: z.string().describe('Measurement units (e.g. "cents per kilowatt-hour").'),
49
+ })
50
+ .describe('A data column available for this route.'))
51
+ .describe('Data columns available for this route.'),
52
+ frequencies: z
53
+ .array(z
54
+ .object({
55
+ id: z.string().describe('Frequency ID (e.g. "monthly", "annual").'),
56
+ description: z.string().describe('Human-readable description.'),
57
+ query: z.string().describe('API query value for this frequency.'),
58
+ format: z.string().describe('Period format string (e.g. "YYYY-MM", "YYYY").'),
59
+ })
60
+ .describe('A frequency option for eia_query_route.'))
61
+ .describe('Valid frequency options for eia_query_route.'),
62
+ date_range: z
63
+ .object({
64
+ start: z.string().describe('Earliest available period.'),
65
+ end: z.string().describe('Latest available period.'),
66
+ })
67
+ .describe('Available date range for this route.'),
68
+ default_frequency: z.string().describe('Default frequency ID used when none is specified.'),
69
+ default_date_format: z
70
+ .string()
71
+ .describe('Period format for the default frequency (e.g. "YYYY-MM").'),
72
+ }),
73
+ errors: [
74
+ {
75
+ reason: 'route_not_found',
76
+ code: JsonRpcErrorCode.NotFound,
77
+ when: 'Route does not exist in the EIA taxonomy.',
78
+ recovery: 'Use eia_browse_routes or eia_search_routes to discover valid leaf route paths.',
79
+ },
80
+ {
81
+ reason: 'route_not_queryable',
82
+ code: JsonRpcErrorCode.ValidationError,
83
+ when: 'Route is a category node with sub-routes, not a queryable leaf.',
84
+ recovery: 'Use eia_browse_routes to drill into sub-routes, or eia_search_routes to find leaf routes.',
85
+ },
86
+ {
87
+ reason: 'rate_limited',
88
+ code: JsonRpcErrorCode.ServiceUnavailable,
89
+ retryable: true,
90
+ when: 'EIA rate limit hit during facet fan-out.',
91
+ recovery: 'Back off and retry; use a production EIA API key for higher rate limits.',
92
+ },
93
+ ],
94
+ async handler(input, ctx) {
95
+ ctx.log.info('Executing eia_describe_route', { route: input.route });
96
+ const service = getEiaApiService();
97
+ const meta = await service.describe(input.route, ctx);
98
+ return {
99
+ route: meta.route,
100
+ description: meta.description,
101
+ facets: meta.facets.map((f) => ({
102
+ id: f.id,
103
+ description: f.description,
104
+ values: f.values.map((v) => ({
105
+ id: v.id,
106
+ name: v.name,
107
+ ...(v.alias !== undefined && { alias: v.alias }),
108
+ })),
109
+ })),
110
+ data_columns: meta.dataColumns.map((c) => ({
111
+ id: c.id,
112
+ alias: c.alias,
113
+ units: c.units,
114
+ })),
115
+ frequencies: meta.frequencies.map((freq) => ({
116
+ id: freq.id,
117
+ description: freq.description,
118
+ query: freq.query,
119
+ format: freq.format,
120
+ })),
121
+ date_range: {
122
+ start: meta.dateRange.start,
123
+ end: meta.dateRange.end,
124
+ },
125
+ default_frequency: meta.defaultFrequency,
126
+ default_date_format: meta.defaultDateFormat,
127
+ };
128
+ },
129
+ format: (result) => {
130
+ const lines = [];
131
+ lines.push(`## ${result.route}`);
132
+ if (result.description)
133
+ lines.push(`\n${result.description}\n`);
134
+ lines.push(`**Date range:** ${result.date_range.start} → ${result.date_range.end}`);
135
+ lines.push(`**Default frequency:** ${result.default_frequency} (format: ${result.default_date_format})\n`);
136
+ if (result.data_columns.length) {
137
+ lines.push('### Data columns');
138
+ for (const col of result.data_columns) {
139
+ lines.push(`- **${col.id}** (${col.alias}) — ${col.units}`);
140
+ }
141
+ lines.push('');
142
+ }
143
+ if (result.frequencies.length) {
144
+ lines.push('### Frequencies');
145
+ for (const freq of result.frequencies) {
146
+ lines.push(`- **${freq.id}** (query: ${freq.query}) — ${freq.description} (format: ${freq.format})`);
147
+ }
148
+ lines.push('');
149
+ }
150
+ if (result.facets.length) {
151
+ lines.push('### Facets (filter dimensions)');
152
+ for (const facet of result.facets) {
153
+ const preview = facet.values.slice(0, 5);
154
+ const more = facet.values.length > 5 ? ` (+${facet.values.length - 5} more)` : '';
155
+ const valueList = preview
156
+ .map((v) => (v.alias ? `${v.id}=${v.name} (${v.alias})` : `${v.id}=${v.name}`))
157
+ .join(', ');
158
+ lines.push(`- **${facet.id}**: ${facet.description} — ${valueList}${more}`);
159
+ }
160
+ }
161
+ return [{ type: 'text', text: lines.join('\n') }];
162
+ },
163
+ });
164
+ //# sourceMappingURL=describe-route.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-route.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/describe-route.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @fileoverview Tool definition for eia_query_route. Fetches data from a leaf
3
+ * route with optional facet filters, date range, frequency, and column
4
+ * selection. Data values come back as strings per the EIA API. Large result
5
+ * sets spill to a DataCanvas table (when CANVAS_PROVIDER_TYPE=duckdb) and
6
+ * return a canvas_id + dataset handle for SQL analysis via eia_dataframe_query.
7
+ * @module mcp-server/tools/definitions/query-route.tool
8
+ */
9
+ import { z } from '@cyanheads/mcp-ts-core';
10
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
11
+ export declare const queryRouteTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
12
+ route: z.ZodString;
13
+ filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
14
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
15
+ frequency: z.ZodOptional<z.ZodString>;
16
+ start: z.ZodOptional<z.ZodString>;
17
+ end: z.ZodOptional<z.ZodString>;
18
+ sort: z.ZodOptional<z.ZodArray<z.ZodObject<{
19
+ column: z.ZodString;
20
+ direction: z.ZodEnum<{
21
+ asc: "asc";
22
+ desc: "desc";
23
+ }>;
24
+ }, z.core.$strip>>>;
25
+ offset: z.ZodDefault<z.ZodNumber>;
26
+ length: z.ZodDefault<z.ZodNumber>;
27
+ canvas_id: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ route: z.ZodString;
30
+ data: z.ZodArray<z.ZodObject<{}, z.core.$loose>>;
31
+ total: z.ZodNumber;
32
+ returned_count: z.ZodNumber;
33
+ frequency: z.ZodString;
34
+ date_format: z.ZodString;
35
+ canvas_id: z.ZodOptional<z.ZodString>;
36
+ dataset: z.ZodOptional<z.ZodString>;
37
+ canvas_preview_note: z.ZodOptional<z.ZodString>;
38
+ truncation_warning: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strip>, readonly [{
40
+ readonly reason: "route_not_found";
41
+ readonly code: JsonRpcErrorCode.NotFound;
42
+ readonly when: "Route does not exist or is not a leaf.";
43
+ readonly recovery: "Use eia_browse_routes or eia_search_routes to find a valid leaf route path.";
44
+ }, {
45
+ readonly reason: "invalid_facet";
46
+ readonly code: JsonRpcErrorCode.ValidationError;
47
+ readonly when: "An unknown facet key was used in filters.";
48
+ readonly recovery: "Call eia_describe_route to see valid facet IDs for this route.";
49
+ }, {
50
+ readonly reason: "no_data";
51
+ readonly code: JsonRpcErrorCode.NotFound;
52
+ readonly when: "Route exists but filters yield zero rows.";
53
+ readonly recovery: "Broaden filters, remove date constraints, or call eia_describe_route to verify facet values — an invalid facet value silently returns zero rows.";
54
+ }, {
55
+ readonly reason: "length_exceeded";
56
+ readonly code: JsonRpcErrorCode.ValidationError;
57
+ readonly when: "length parameter exceeds the EIA maximum of 5000.";
58
+ readonly recovery: "Reduce length to 5000 or use offset pagination for larger result sets.";
59
+ }, {
60
+ readonly reason: "rate_limited";
61
+ readonly code: JsonRpcErrorCode.ServiceUnavailable;
62
+ readonly retryable: true;
63
+ readonly when: "EIA rate limit hit (OVER_RATE_LIMIT).";
64
+ readonly recovery: "Back off and retry; use a production EIA API key for higher rate limits.";
65
+ }]>;
66
+ //# sourceMappingURL=query-route.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-route.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/query-route.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgUzB,CAAC"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * @fileoverview Tool definition for eia_query_route. Fetches data from a leaf
3
+ * route with optional facet filters, date range, frequency, and column
4
+ * selection. Data values come back as strings per the EIA API. Large result
5
+ * sets spill to a DataCanvas table (when CANVAS_PROVIDER_TYPE=duckdb) and
6
+ * return a canvas_id + dataset handle for SQL analysis via eia_dataframe_query.
7
+ * @module mcp-server/tools/definitions/query-route.tool
8
+ */
9
+ import { tool, z } from '@cyanheads/mcp-ts-core';
10
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
11
+ import { getCanvasBridge } from '../../../services/canvas-bridge/canvas-bridge.js';
12
+ import { getEiaApiService } from '../../../services/eia/eia-service.js';
13
+ export const queryRouteTool = tool('eia_query_route', {
14
+ title: 'Query EIA Route Data',
15
+ description: 'Fetches data from a leaf route with optional facet filters, date range, frequency, and column selection. Use eia_describe_route first to discover valid facet IDs, facet values, column IDs, and frequency codes. Data values are strings in the response (EIA API returns all numeric values as strings, e.g. "9.13"); cast to DOUBLE in SQL when arithmetic is needed. Returns a preview inline; large result sets (total > length) spill to a DataCanvas table when canvas is enabled — use the returned canvas_id and dataset name with eia_dataframe_query for SQL analysis. Pass the same canvas_id on subsequent eia_query_route calls to accumulate multiple route results into one canvas for cross-route joins.',
16
+ annotations: { readOnlyHint: true, openWorldHint: false },
17
+ input: z.object({
18
+ route: z
19
+ .string()
20
+ .min(1)
21
+ .describe('Leaf route path (e.g. "electricity/retail-sales", "steo"). Discoverable via eia_browse_routes or eia_search_routes.'),
22
+ filters: z
23
+ .record(z.string(), z.union([z.string(), z.array(z.string())]))
24
+ .optional()
25
+ .describe('Facet filters keyed by facet ID (e.g. { "stateid": "TX", "sectorid": ["RES", "COM"] }). Use the facets[].id values returned by eia_describe_route as keys here.'),
26
+ columns: z
27
+ .array(z.string())
28
+ .optional()
29
+ .describe('Data column IDs to return (reduces payload). Defaults to all. IDs discoverable via eia_describe_route.'),
30
+ frequency: z
31
+ .string()
32
+ .optional()
33
+ .describe('Aggregation frequency ID (e.g. "monthly", "annual"). Defaults to route default. Valid IDs from eia_describe_route.'),
34
+ start: z
35
+ .string()
36
+ .optional()
37
+ .describe('Period start in the route date format (e.g. "2020-01" for monthly, "2020" for annual). Format from eia_describe_route.'),
38
+ end: z.string().optional().describe('Period end (same format as start).'),
39
+ sort: z
40
+ .array(z
41
+ .object({
42
+ column: z.string().describe('Column ID to sort by.'),
43
+ direction: z.enum(['asc', 'desc']).describe('Sort direction.'),
44
+ })
45
+ .describe('A sort criterion.'))
46
+ .optional()
47
+ .describe('Result ordering.'),
48
+ offset: z.number().int().min(0).default(0).describe('Pagination offset (default 0).'),
49
+ length: z
50
+ .number()
51
+ .int()
52
+ .min(1)
53
+ .max(5000)
54
+ .default(100)
55
+ .describe('Rows to fetch per request (default 100, max 5000 per EIA limit).'),
56
+ canvas_id: z
57
+ .string()
58
+ .optional()
59
+ .describe('DataCanvas ID to register results into. Omit on first call — a new canvas is minted and returned. Pass the returned canvas_id on subsequent calls to accumulate multiple route results into one canvas for cross-route SQL joins.'),
60
+ }),
61
+ output: z.object({
62
+ route: z.string().describe('The route path queried.'),
63
+ data: z
64
+ .array(z.object({}).passthrough().describe('A single data row with dynamic column keys.'))
65
+ .describe('Preview rows. All numeric values are strings per the EIA API (e.g. "9.13"). Cast to DOUBLE in SQL for arithmetic: CAST(value AS DOUBLE). Per-column units appear as {col}-units fields inline in each row. Keys are dynamic column IDs from the EIA route.'),
66
+ total: z.number().describe('Total matching rows in the EIA dataset.'),
67
+ returned_count: z
68
+ .number()
69
+ .describe('Rows in this response. When returned_count < total, use offset or canvas for the rest.'),
70
+ frequency: z.string().describe('Frequency of the returned data.'),
71
+ date_format: z.string().describe('Period format for the returned data (e.g. "YYYY-MM").'),
72
+ canvas_id: z
73
+ .string()
74
+ .optional()
75
+ .describe('Canvas workspace ID — present when spillover occurred or canvas_id was supplied. Pass to subsequent eia_query_route calls to accumulate datasets.'),
76
+ dataset: z
77
+ .string()
78
+ .optional()
79
+ .describe('df_<id> table handle for the registered dataset — pass directly to eia_dataframe_query SQL (SELECT ... FROM df_<id>).'),
80
+ canvas_preview_note: z
81
+ .string()
82
+ .optional()
83
+ .describe('Human-readable note when total > returned rows, describing how to access the full dataset via canvas SQL.'),
84
+ truncation_warning: z
85
+ .string()
86
+ .optional()
87
+ .describe("Forwarded from EIA's warnings[] when the API warns of truncated results near the 5,000 per-page limit."),
88
+ }),
89
+ errors: [
90
+ {
91
+ reason: 'route_not_found',
92
+ code: JsonRpcErrorCode.NotFound,
93
+ when: 'Route does not exist or is not a leaf.',
94
+ recovery: 'Use eia_browse_routes or eia_search_routes to find a valid leaf route path.',
95
+ },
96
+ {
97
+ reason: 'invalid_facet',
98
+ code: JsonRpcErrorCode.ValidationError,
99
+ when: 'An unknown facet key was used in filters.',
100
+ recovery: 'Call eia_describe_route to see valid facet IDs for this route.',
101
+ },
102
+ {
103
+ reason: 'no_data',
104
+ code: JsonRpcErrorCode.NotFound,
105
+ when: 'Route exists but filters yield zero rows.',
106
+ recovery: 'Broaden filters, remove date constraints, or call eia_describe_route to verify facet values — an invalid facet value silently returns zero rows.',
107
+ },
108
+ {
109
+ reason: 'length_exceeded',
110
+ code: JsonRpcErrorCode.ValidationError,
111
+ when: 'length parameter exceeds the EIA maximum of 5000.',
112
+ recovery: 'Reduce length to 5000 or use offset pagination for larger result sets.',
113
+ },
114
+ {
115
+ reason: 'rate_limited',
116
+ code: JsonRpcErrorCode.ServiceUnavailable,
117
+ retryable: true,
118
+ when: 'EIA rate limit hit (OVER_RATE_LIMIT).',
119
+ recovery: 'Back off and retry; use a production EIA API key for higher rate limits.',
120
+ },
121
+ ],
122
+ async handler(input, ctx) {
123
+ ctx.log.info('Executing eia_query_route', {
124
+ route: input.route,
125
+ offset: input.offset,
126
+ length: input.length,
127
+ });
128
+ // Pre-flight: detect inverted date range before hitting the EIA API.
129
+ // ISO date strings (YYYY-MM, YYYY-MM-DD, YYYY) sort lexicographically, so
130
+ // string comparison is sufficient for this check.
131
+ if (input.start !== undefined && input.end !== undefined && input.start > input.end) {
132
+ throw ctx.fail('no_data', `Date range is inverted: start "${input.start}" is after end "${input.end}". Swap start and end to retrieve data.`, {
133
+ route: input.route,
134
+ start: input.start,
135
+ end: input.end,
136
+ ...ctx.recoveryFor('no_data'),
137
+ });
138
+ }
139
+ const service = getEiaApiService();
140
+ const dataResp = await service.query(input.route, {
141
+ ...(input.filters !== undefined && { filters: input.filters }),
142
+ ...(input.columns !== undefined && { columns: input.columns }),
143
+ ...(input.frequency !== undefined && { frequency: input.frequency }),
144
+ ...(input.start !== undefined && { start: input.start }),
145
+ ...(input.end !== undefined && { end: input.end }),
146
+ ...(input.sort !== undefined && { sort: input.sort }),
147
+ offset: input.offset,
148
+ length: input.length,
149
+ }, ctx);
150
+ if (dataResp.total === 0 && dataResp.data.length === 0) {
151
+ throw ctx.fail('no_data', `Route "${input.route}" returned zero rows for the given filters.`, {
152
+ route: input.route,
153
+ ...ctx.recoveryFor('no_data'),
154
+ });
155
+ }
156
+ const result = {
157
+ route: input.route,
158
+ data: dataResp.data,
159
+ total: dataResp.total,
160
+ returned_count: dataResp.data.length,
161
+ frequency: dataResp.frequency,
162
+ date_format: dataResp.dateFormat,
163
+ };
164
+ // Forward EIA server-side truncation warnings
165
+ if (dataResp.warnings?.length) {
166
+ result.truncation_warning = dataResp.warnings.join('; ');
167
+ }
168
+ // DataCanvas spillover — opt-in via CANVAS_PROVIDER_TYPE=duckdb
169
+ const bridge = getCanvasBridge();
170
+ if (bridge && dataResp.data.length > 0) {
171
+ const registered = await bridge.registerDataframe(ctx, {
172
+ rows: dataResp.data,
173
+ sourceTool: 'eia_query_route',
174
+ queryParams: {
175
+ route: input.route,
176
+ filters: input.filters,
177
+ columns: input.columns,
178
+ frequency: input.frequency,
179
+ start: input.start,
180
+ end: input.end,
181
+ offset: input.offset,
182
+ length: input.length,
183
+ },
184
+ truncated: dataResp.total > dataResp.data.length,
185
+ maxRows: input.length,
186
+ });
187
+ if (registered) {
188
+ result.dataset = registered.tableName;
189
+ // Acquire or reuse canvas ID
190
+ if (input.canvas_id) {
191
+ result.canvas_id = input.canvas_id;
192
+ }
193
+ else {
194
+ // The bridge manages the canvas internally; surface a stable per-tenant ID
195
+ // by using the bridge's shared canvas. We mint the canvas_id from the
196
+ // dataset name prefix for consistency.
197
+ result.canvas_id = registered.tableName;
198
+ }
199
+ if (dataResp.total > dataResp.data.length) {
200
+ result.canvas_preview_note = `Showing ${dataResp.data.length.toLocaleString()} of ${dataResp.total.toLocaleString()} rows — query canvas for full dataset using: SELECT * FROM ${registered.tableName}`;
201
+ }
202
+ }
203
+ }
204
+ else if (dataResp.total > dataResp.data.length) {
205
+ result.canvas_preview_note = `Showing ${dataResp.data.length.toLocaleString()} of ${dataResp.total.toLocaleString()} rows — enable DataCanvas (CANVAS_PROVIDER_TYPE=duckdb) for full dataset access, or use offset pagination.`;
206
+ }
207
+ return result;
208
+ },
209
+ format: (result) => {
210
+ const lines = [];
211
+ lines.push(`## Query: ${result.route}`);
212
+ lines.push(`**Frequency:** ${result.frequency} | **Date format:** ${result.date_format}`);
213
+ lines.push(`**Rows:** ${result.returned_count} of ${result.total} total\n`);
214
+ if (result.canvas_preview_note) {
215
+ lines.push(`> ${result.canvas_preview_note}\n`);
216
+ }
217
+ if (result.dataset) {
218
+ lines.push(`**Dataset:** \`${result.dataset}\` (canvas: ${result.canvas_id})`);
219
+ lines.push('Use `eia_dataframe_query` with this dataset name for full SQL access.\n');
220
+ }
221
+ if (result.truncation_warning) {
222
+ lines.push(`> **Warning:** ${result.truncation_warning}\n`);
223
+ }
224
+ if (result.data.length === 0) {
225
+ lines.push('_No rows returned._');
226
+ return [{ type: 'text', text: lines.join('\n') }];
227
+ }
228
+ // Render table — separate data columns from {col}-units columns.
229
+ // Units are identical on every row, so annotate the header instead of
230
+ // repeating them in the body.
231
+ const firstRow = result.data[0];
232
+ if (!firstRow)
233
+ return [{ type: 'text', text: lines.join('\n') }];
234
+ const allKeys = Object.keys(firstRow);
235
+ // Build a units map from the first row: { colName: "unit string" }
236
+ const unitsMap = {};
237
+ for (const key of allKeys) {
238
+ if (key.endsWith('-units')) {
239
+ const col = key.slice(0, -6); // strip trailing '-units'
240
+ const unit = firstRow[key];
241
+ if (unit !== null && unit !== undefined)
242
+ unitsMap[col] = String(unit);
243
+ }
244
+ }
245
+ // Data columns are all keys that are not {col}-units entries
246
+ const dataCols = allKeys.filter((k) => !k.endsWith('-units'));
247
+ // Header: "col (unit)" when a unit is known, else just "col"
248
+ const headerCells = dataCols.map((c) => (unitsMap[c] ? `${c} (${unitsMap[c]})` : c));
249
+ const header = `| ${headerCells.join(' | ')} |`;
250
+ const sep = `| ${dataCols.map(() => '---').join(' | ')} |`;
251
+ lines.push(header, sep);
252
+ for (const row of result.data) {
253
+ const cells = dataCols.map((c) => {
254
+ const v = row[c];
255
+ if (v === null || v === undefined)
256
+ return '';
257
+ return String(v).replace(/\|/g, '\\|');
258
+ });
259
+ lines.push(`| ${cells.join(' | ')} |`);
260
+ }
261
+ return [{ type: 'text', text: lines.join('\n') }];
262
+ },
263
+ });
264
+ //# sourceMappingURL=query-route.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-route.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/query-route.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Tool definition for eia_search_routes. Fuzzy text search across
3
+ * route names, descriptions, and category labels using an in-memory Fuse.js
4
+ * index. Resolves natural-language queries to route paths. STEO series names
5
+ * (1,469 entries) are included in the index for series discovery.
6
+ * @module mcp-server/tools/definitions/search-routes.tool
7
+ */
8
+ import { z } from '@cyanheads/mcp-ts-core';
9
+ export declare const searchRoutesTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
10
+ query: z.ZodString;
11
+ limit: z.ZodDefault<z.ZodNumber>;
12
+ }, z.core.$strip>, z.ZodObject<{
13
+ results: z.ZodArray<z.ZodObject<{
14
+ route: z.ZodString;
15
+ name: z.ZodString;
16
+ description: z.ZodString;
17
+ score: z.ZodNumber;
18
+ isLeaf: z.ZodBoolean;
19
+ filter_hint: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
+ }, z.core.$strip>>;
21
+ total_indexed: z.ZodNumber;
22
+ }, z.core.$strip>, undefined>;
23
+ //# sourceMappingURL=search-routes.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-routes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-routes.tool.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAGjD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;6BAmG3B,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @fileoverview Tool definition for eia_search_routes. Fuzzy text search across
3
+ * route names, descriptions, and category labels using an in-memory Fuse.js
4
+ * index. Resolves natural-language queries to route paths. STEO series names
5
+ * (1,469 entries) are included in the index for series discovery.
6
+ * @module mcp-server/tools/definitions/search-routes.tool
7
+ */
8
+ import { tool, z } from '@cyanheads/mcp-ts-core';
9
+ import { getEiaApiService } from '../../../services/eia/eia-service.js';
10
+ export const searchRoutesTool = tool('eia_search_routes', {
11
+ title: 'Search EIA Routes',
12
+ description: 'Fuzzy text search across route names, descriptions, and category labels. Resolves natural-language queries like "electricity retail sales by state" or "natural gas imports" to matching route paths. STEO series names are indexed so queries like "ethanol net imports" or "crude oil production forecast" also resolve. Results include isLeaf so you know whether to browse further or query directly. Results with score > 0.5 are weak matches — try a more specific query or use eia_browse_routes to explore the taxonomy.',
13
+ annotations: { readOnlyHint: true, openWorldHint: false },
14
+ input: z.object({
15
+ query: z
16
+ .string()
17
+ .min(1)
18
+ .describe('Free-text search terms to match against route names and descriptions.'),
19
+ limit: z
20
+ .number()
21
+ .int()
22
+ .min(1)
23
+ .max(30)
24
+ .default(10)
25
+ .describe('Maximum results to return (default 10, max 30).'),
26
+ }),
27
+ output: z.object({
28
+ results: z
29
+ .array(z
30
+ .object({
31
+ route: z
32
+ .string()
33
+ .describe('Route path — usable directly in eia_describe_route or eia_query_route.'),
34
+ name: z.string().describe('Human-readable route name.'),
35
+ description: z.string().describe('Route description.'),
36
+ score: z
37
+ .number()
38
+ .describe('Fuzzy match score: 0 = exact, 1 = no match. Lower is better.'),
39
+ isLeaf: z
40
+ .boolean()
41
+ .describe('True when the route is a queryable leaf; false when it has sub-routes to browse.'),
42
+ filter_hint: z
43
+ .record(z.string(), z.string())
44
+ .optional()
45
+ .describe('Pre-built filter for eia_query_route when a specific facet value is required. Present on STEO series results — pass directly as filters (e.g. eia_query_route(route="steo", filters=filter_hint)).'),
46
+ })
47
+ .describe('A search result entry.'))
48
+ .describe('Ranked matches, best first.'),
49
+ total_indexed: z
50
+ .number()
51
+ .describe('Total entries in the search index (routes + STEO series names).'),
52
+ }),
53
+ async handler(input, ctx) {
54
+ ctx.log.info('Executing eia_search_routes', { query: input.query, limit: input.limit });
55
+ const service = getEiaApiService();
56
+ const { results, totalIndexed } = await service.search(input.query, input.limit, ctx);
57
+ return {
58
+ results: results.map(({ entry, score }) => ({
59
+ route: entry.route,
60
+ name: entry.name,
61
+ description: entry.description,
62
+ score,
63
+ isLeaf: entry.isLeaf,
64
+ ...(entry.filter_hint !== undefined && { filter_hint: entry.filter_hint }),
65
+ })),
66
+ total_indexed: totalIndexed,
67
+ };
68
+ },
69
+ format: (result) => {
70
+ const lines = [];
71
+ if (result.results.length === 0) {
72
+ lines.push('No matching routes found. Try different search terms or browse with eia_browse_routes.');
73
+ lines.push(`\nIndex size: ${result.total_indexed} entries`);
74
+ return [{ type: 'text', text: lines.join('\n') }];
75
+ }
76
+ lines.push(`**${result.results.length} result(s)** (index: ${result.total_indexed} entries)\n`);
77
+ for (const r of result.results) {
78
+ const tag = r.isLeaf ? '[leaf]' : '[cat]';
79
+ const weakMatch = r.score > 0.5 ? ' ⚠ weak match' : '';
80
+ lines.push(`${tag} **${r.route}** (score: ${r.score.toFixed(3)}${weakMatch})`);
81
+ lines.push(` ${r.name}`);
82
+ if (r.description)
83
+ lines.push(` ${r.description}`);
84
+ if (r.filter_hint) {
85
+ const hint = Object.entries(r.filter_hint)
86
+ .map(([k, v]) => `"${k}": "${v}"`)
87
+ .join(', ');
88
+ lines.push(` Query with: \`eia_query_route(route="${r.route}", filters={${hint}})\``);
89
+ }
90
+ }
91
+ return [{ type: 'text', text: lines.join('\n') }];
92
+ },
93
+ });
94
+ //# sourceMappingURL=search-routes.tool.js.map