@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,168 @@
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
+ let _cache;
12
+ /**
13
+ * Normalize a description string from the EIA API. EIA descriptions often
14
+ * contain embedded `\r\n` + leading whitespace (source-level line wrapping).
15
+ * Collapse to a clean single-line string.
16
+ */
17
+ export function normalizeDescription(desc) {
18
+ if (!desc)
19
+ return '';
20
+ return desc
21
+ .replace(/\r/g, '')
22
+ .replace(/\s*\n\s*/g, ' ')
23
+ .replace(/\s{2,}/g, ' ')
24
+ .trim();
25
+ }
26
+ /** Walk the raw route tree and collect all nodes into a flat path→node map. */
27
+ export function buildNodeMap(nodes, parentPath, map) {
28
+ for (const node of nodes) {
29
+ const path = parentPath ? `${parentPath}/${node.id}` : node.id;
30
+ // Normalize description in place so all tools that read from cache get clean strings
31
+ const normalized = node.description !== undefined
32
+ ? { ...node, description: normalizeDescription(node.description) }
33
+ : node;
34
+ map.set(path, normalized);
35
+ if (normalized.routes?.length) {
36
+ buildNodeMap(normalized.routes, path, map);
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Classify a raw node as a leaf. A node is a leaf when it has `frequency`,
42
+ * `facets`, or `data` fields (queryable data endpoint) rather than a `routes`
43
+ * array. Root-level nodes with no sub-routes and no data fields are treated as
44
+ * leaves (e.g. steo).
45
+ */
46
+ export function isLeafNode(node) {
47
+ if (node.frequency !== undefined)
48
+ return true;
49
+ if (node.facets !== undefined)
50
+ return true;
51
+ if (node.data !== undefined)
52
+ return true;
53
+ // A node with no routes array and no data/frequency is still a leaf candidate
54
+ return !node.routes?.length;
55
+ }
56
+ /** Build search index entries from a flat node map. */
57
+ function buildEntries(nodeMap) {
58
+ const entries = [];
59
+ for (const [route, node] of nodeMap) {
60
+ const parts = route.split('/');
61
+ const category = parts.length > 1 ? parts[0] : undefined;
62
+ entries.push({
63
+ route,
64
+ name: node.name,
65
+ description: node.description ?? '',
66
+ isLeaf: isLeafNode(node),
67
+ category,
68
+ });
69
+ }
70
+ return entries;
71
+ }
72
+ const FUSE_OPTIONS = {
73
+ keys: [
74
+ { name: 'name', weight: 2 },
75
+ { name: 'description', weight: 1.5 },
76
+ { name: 'route', weight: 1 },
77
+ { name: 'category', weight: 0.5 },
78
+ ],
79
+ threshold: 0.4,
80
+ includeScore: true,
81
+ minMatchCharLength: 2,
82
+ };
83
+ /** Initialize the cache with the fetched route tree and optional STEO entries. */
84
+ export function initRouteCache(topLevelNodes, steoSeriesEntries) {
85
+ const nodeMap = new Map();
86
+ buildNodeMap(topLevelNodes, '', nodeMap);
87
+ const routeEntries = buildEntries(nodeMap);
88
+ const allEntries = [...routeEntries, ...steoSeriesEntries];
89
+ _cache = {
90
+ nodeMap,
91
+ fuseIndex: new Fuse(allEntries, FUSE_OPTIONS),
92
+ entries: allEntries,
93
+ };
94
+ }
95
+ /** Return the cache, throwing if not yet initialized. */
96
+ export function getRouteCache() {
97
+ if (!_cache)
98
+ throw new Error('Route cache not initialized');
99
+ return _cache;
100
+ }
101
+ /** True when the cache has been populated. */
102
+ export function isRouteCacheReady() {
103
+ return _cache !== undefined;
104
+ }
105
+ /** Reset the cache (used in tests). */
106
+ export function _resetRouteCache() {
107
+ _cache = undefined;
108
+ }
109
+ /** Get a node by route path. Returns undefined when not found. */
110
+ export function getNode(path) {
111
+ if (!_cache)
112
+ return;
113
+ if (!path) {
114
+ // Root — return a synthetic node with top-level children
115
+ return;
116
+ }
117
+ return _cache.nodeMap.get(path);
118
+ }
119
+ /**
120
+ * Get children of a given path. For root (empty path), returns top-level nodes.
121
+ * Returns empty array when path has no children.
122
+ */
123
+ export function getChildren(path) {
124
+ if (!_cache)
125
+ return [];
126
+ const children = [];
127
+ if (!path) {
128
+ // Root: find all nodes whose route has no '/' separator
129
+ for (const [route, node] of _cache.nodeMap) {
130
+ if (!route.includes('/')) {
131
+ children.push({ id: node.id, route, node });
132
+ }
133
+ }
134
+ }
135
+ else {
136
+ // Find all nodes whose route is exactly `${path}/${id}`
137
+ const prefix = `${path}/`;
138
+ for (const [route, node] of _cache.nodeMap) {
139
+ if (route.startsWith(prefix) && !route.slice(prefix.length).includes('/')) {
140
+ const id = route.slice(prefix.length);
141
+ children.push({ id, route, node });
142
+ }
143
+ }
144
+ }
145
+ return children;
146
+ }
147
+ /** Fuzzy search across the index. Returns ranked matches. */
148
+ export function searchRoutes(query, limit) {
149
+ if (!_cache)
150
+ return [];
151
+ const results = _cache.fuseIndex.search(query, { limit });
152
+ return results.map((r) => ({
153
+ entry: r.item,
154
+ score: r.score ?? 1,
155
+ }));
156
+ }
157
+ /** Total number of indexed entries. */
158
+ export function getIndexSize() {
159
+ return _cache?.entries.length ?? 0;
160
+ }
161
+ /** Add STEO series entries to an already-initialized cache. */
162
+ export function addSteoSeriesToIndex(steoEntries) {
163
+ if (!_cache)
164
+ return;
165
+ _cache.entries.push(...steoEntries);
166
+ _cache.fuseIndex = new Fuse(_cache.entries, FUSE_OPTIONS);
167
+ }
168
+ //# sourceMappingURL=route-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-cache.js","sourceRoot":"","sources":["../../../src/services/eia/route-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @fileoverview EIA API v2 domain types — raw API shapes and normalized domain
3
+ * objects used across the service layer and tool definitions.
4
+ * @module services/eia/types
5
+ */
6
+ /** A single entry in the EIA route tree (not yet classified as leaf/category). */
7
+ export interface RouteEntry {
8
+ description: string;
9
+ id: string;
10
+ /** True when this route is a leaf (queryable data endpoint). */
11
+ isLeaf: boolean;
12
+ name: string;
13
+ route: string;
14
+ }
15
+ /** Full route tree node as returned by the EIA v2 API browse endpoint. */
16
+ export interface RawRouteNode {
17
+ /**
18
+ * Present on leaf nodes. Two shapes exist in the wild:
19
+ * Standard: `{ colId: { alias: string, units: string }, ... }`
20
+ * Value-array: `{ value: [] }` — time-series routes with a single unnamed column
21
+ */
22
+ data?: Record<string, {
23
+ alias: string;
24
+ units: string;
25
+ } | unknown[]>;
26
+ defaultDateFormat?: string;
27
+ defaultFrequency?: string;
28
+ description?: string;
29
+ endPeriod?: string;
30
+ /** Present on leaf nodes */
31
+ facets?: RawFacetMeta[];
32
+ /** Present on leaf nodes */
33
+ frequency?: RawFrequency[];
34
+ id: string;
35
+ name: string;
36
+ routes?: RawRouteNode[];
37
+ startPeriod?: string;
38
+ }
39
+ export interface RawFrequency {
40
+ description: string;
41
+ format: string;
42
+ id: string;
43
+ query: string;
44
+ }
45
+ /** Facet metadata from the route metadata endpoint (no values). */
46
+ export interface RawFacetMeta {
47
+ description: string;
48
+ id: string;
49
+ }
50
+ /** Individual facet value from /v2/{route}/facet/{facetId}. */
51
+ export interface RawFacetValue {
52
+ alias?: string;
53
+ id: string;
54
+ name: string;
55
+ }
56
+ /** Response from /v2/{route}/facet/{facetId}. */
57
+ export interface RawFacetResponse {
58
+ facets: RawFacetValue[];
59
+ totalFacets: number;
60
+ }
61
+ /** Normalized facet with all values populated. */
62
+ export interface Facet {
63
+ description: string;
64
+ id: string;
65
+ values: Array<{
66
+ id: string;
67
+ name: string;
68
+ alias?: string;
69
+ }>;
70
+ }
71
+ /** Normalized data column. */
72
+ export interface DataColumn {
73
+ alias: string;
74
+ id: string;
75
+ units: string;
76
+ }
77
+ /** Full normalized route metadata (leaf only). */
78
+ export interface RouteMetadata {
79
+ dataColumns: DataColumn[];
80
+ dateRange: {
81
+ start: string;
82
+ end: string;
83
+ };
84
+ defaultDateFormat: string;
85
+ defaultFrequency: string;
86
+ description: string;
87
+ facets: Facet[];
88
+ frequencies: RawFrequency[];
89
+ route: string;
90
+ }
91
+ /** A data row from /v2/{route}/data/. All values are strings per EIA API. */
92
+ export type DataRow = Record<string, string | null>;
93
+ /** Response from /v2/{route}/data/. */
94
+ export interface DataResponse {
95
+ data: DataRow[];
96
+ dateFormat: string;
97
+ frequency: string;
98
+ total: number;
99
+ warnings: string[] | undefined;
100
+ }
101
+ /** Entry in the Fuse.js search index. */
102
+ export interface SearchIndexEntry {
103
+ category: string | undefined;
104
+ description: string;
105
+ /**
106
+ * Pre-built filter hint for routes that require a specific facet value to
107
+ * query. Present on STEO series entries so callers can pass the seriesId
108
+ * directly to eia_query_route without parsing the description string.
109
+ */
110
+ filter_hint?: Record<string, string>;
111
+ isLeaf: boolean;
112
+ name: string;
113
+ route: string;
114
+ }
115
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/eia/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview EIA API v2 domain types — raw API shapes and normalized domain
3
+ * objects used across the service layer and tool definitions.
4
+ * @module services/eia/types
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/eia/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@cyanheads/eia-energy-mcp-server",
3
+ "version": "0.2.0",
4
+ "mcpName": "io.github.cyanheads/eia-energy-mcp-server",
5
+ "description": "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.",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "eia-energy-mcp-server": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "changelog/",
14
+ "dist/",
15
+ "README.md",
16
+ "LICENSE",
17
+ "CLAUDE.md",
18
+ "Dockerfile",
19
+ "server.json"
20
+ ],
21
+ "scripts": {
22
+ "build": "bun run scripts/build.ts",
23
+ "rebuild": "bun run scripts/clean.ts && bun run scripts/build.ts",
24
+ "clean": "bun run scripts/clean.ts",
25
+ "devcheck": "bun run scripts/devcheck.ts",
26
+ "audit:refresh": "rm -f bun.lock && bun install && bun audit",
27
+ "tree": "bun run scripts/tree.ts",
28
+ "format": "biome check --write --unsafe .",
29
+ "lint:mcp": "bun run scripts/lint-mcp.ts",
30
+ "lint:packaging": "bun run scripts/lint-packaging.ts",
31
+ "bundle": "bun run build && npx -y @anthropic-ai/mcpb pack . dist/eia-energy-mcp-server.mcpb",
32
+ "changelog:build": "bun run scripts/build-changelog.ts",
33
+ "changelog:check": "bun run scripts/build-changelog.ts --check",
34
+ "list-skills": "bun run scripts/list-skills.ts",
35
+ "test": "vitest run",
36
+ "start": "node dist/index.js",
37
+ "start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
38
+ "start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js",
39
+ "publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish"
40
+ },
41
+ "keywords": [
42
+ "eia",
43
+ "energy",
44
+ "energy-information-administration",
45
+ "electricity",
46
+ "petroleum",
47
+ "natural-gas",
48
+ "coal",
49
+ "energy-data",
50
+ "energy-api",
51
+ "steo",
52
+ "us-energy",
53
+ "mcp",
54
+ "mcp-server",
55
+ "model-context-protocol",
56
+ "typescript",
57
+ "bun",
58
+ "ai-agent"
59
+ ],
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/cyanheads/eia-energy-mcp-server.git"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/cyanheads/eia-energy-mcp-server/issues"
66
+ },
67
+ "homepage": "https://github.com/cyanheads/eia-energy-mcp-server#readme",
68
+ "author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/eia-energy-mcp-server#readme)",
69
+ "funding": [
70
+ {
71
+ "type": "github",
72
+ "url": "https://github.com/sponsors/cyanheads"
73
+ },
74
+ {
75
+ "type": "buy_me_a_coffee",
76
+ "url": "https://www.buymeacoffee.com/cyanheads"
77
+ }
78
+ ],
79
+ "license": "Apache-2.0",
80
+ "packageManager": "bun@1.3.0",
81
+ "engines": {
82
+ "bun": ">=1.3.0",
83
+ "node": ">=24.0.0"
84
+ },
85
+ "publishConfig": {
86
+ "access": "public"
87
+ },
88
+ "dependencies": {
89
+ "@cyanheads/mcp-ts-core": "^0.9.6",
90
+ "@duckdb/node-api": "^1.5.3-r.1",
91
+ "fuse.js": "^7.3.0",
92
+ "pino-pretty": "^13.1.3",
93
+ "zod": "^4.4.3"
94
+ },
95
+ "devDependencies": {
96
+ "@biomejs/biome": "^2.4.15",
97
+ "@types/node": "^25.9.1",
98
+ "depcheck": "^1.4.7",
99
+ "ignore": "^7.0.5",
100
+ "tsc-alias": "^1.8.17",
101
+ "typescript": "^6.0.3",
102
+ "vitest": "^4.1.7"
103
+ }
104
+ }
package/server.json ADDED
@@ -0,0 +1,163 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.cyanheads/eia-energy-mcp-server",
4
+ "description": "Browse and query the EIA API v2 — electricity, petroleum, natural gas, coal, forecasts via MCP.",
5
+ "repository": {
6
+ "url": "https://github.com/cyanheads/eia-energy-mcp-server",
7
+ "source": "github"
8
+ },
9
+ "version": "0.2.0",
10
+ "remotes": [
11
+ {
12
+ "type": "streamable-http",
13
+ "url": "https://eia-energy.caseyjhand.com/mcp"
14
+ }
15
+ ],
16
+ "packages": [
17
+ {
18
+ "registryType": "npm",
19
+ "registryBaseUrl": "https://registry.npmjs.org",
20
+ "identifier": "@cyanheads/eia-energy-mcp-server",
21
+ "runtimeHint": "bun",
22
+ "version": "0.2.0",
23
+ "packageArguments": [
24
+ {
25
+ "type": "positional",
26
+ "value": "run"
27
+ },
28
+ {
29
+ "type": "positional",
30
+ "value": "start:stdio"
31
+ }
32
+ ],
33
+ "environmentVariables": [
34
+ {
35
+ "name": "EIA_API_KEY",
36
+ "description": "Free API key from api.eia.gov. Required — all requests use this as the api_key query parameter.",
37
+ "format": "string",
38
+ "isRequired": true
39
+ },
40
+ {
41
+ "name": "EIA_DATASET_TTL_SECONDS",
42
+ "description": "Per-table TTL for DataCanvas dataframes in seconds.",
43
+ "format": "string",
44
+ "isRequired": false,
45
+ "default": "86400"
46
+ },
47
+ {
48
+ "name": "EIA_DATAFRAME_DROP_ENABLED",
49
+ "description": "Set to 'true' to expose eia_dataframe_drop for manual canvas cleanup.",
50
+ "format": "string",
51
+ "isRequired": false,
52
+ "default": "false"
53
+ },
54
+ {
55
+ "name": "CANVAS_PROVIDER_TYPE",
56
+ "description": "Set to 'duckdb' to enable DataCanvas spillover for large query result sets (Node.js only).",
57
+ "format": "string",
58
+ "isRequired": false
59
+ },
60
+ {
61
+ "name": "MCP_LOG_LEVEL",
62
+ "description": "Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').",
63
+ "format": "string",
64
+ "isRequired": false,
65
+ "default": "info"
66
+ }
67
+ ],
68
+ "transport": {
69
+ "type": "stdio"
70
+ }
71
+ },
72
+ {
73
+ "registryType": "npm",
74
+ "registryBaseUrl": "https://registry.npmjs.org",
75
+ "identifier": "@cyanheads/eia-energy-mcp-server",
76
+ "runtimeHint": "bun",
77
+ "version": "0.2.0",
78
+ "packageArguments": [
79
+ {
80
+ "type": "positional",
81
+ "value": "run"
82
+ },
83
+ {
84
+ "type": "positional",
85
+ "value": "start:http"
86
+ }
87
+ ],
88
+ "environmentVariables": [
89
+ {
90
+ "name": "EIA_API_KEY",
91
+ "description": "Free API key from api.eia.gov. Required — all requests use this as the api_key query parameter.",
92
+ "format": "string",
93
+ "isRequired": true
94
+ },
95
+ {
96
+ "name": "EIA_DATASET_TTL_SECONDS",
97
+ "description": "Per-table TTL for DataCanvas dataframes in seconds.",
98
+ "format": "string",
99
+ "isRequired": false,
100
+ "default": "86400"
101
+ },
102
+ {
103
+ "name": "EIA_DATAFRAME_DROP_ENABLED",
104
+ "description": "Set to 'true' to expose eia_dataframe_drop for manual canvas cleanup.",
105
+ "format": "string",
106
+ "isRequired": false,
107
+ "default": "false"
108
+ },
109
+ {
110
+ "name": "CANVAS_PROVIDER_TYPE",
111
+ "description": "Set to 'duckdb' to enable DataCanvas spillover for large query result sets (Node.js only).",
112
+ "format": "string",
113
+ "isRequired": false
114
+ },
115
+ {
116
+ "name": "MCP_HTTP_HOST",
117
+ "description": "The hostname for the HTTP server.",
118
+ "format": "string",
119
+ "isRequired": false,
120
+ "default": "127.0.0.1"
121
+ },
122
+ {
123
+ "name": "MCP_HTTP_PORT",
124
+ "description": "The port to run the HTTP server on.",
125
+ "format": "string",
126
+ "isRequired": false,
127
+ "default": "3010"
128
+ },
129
+ {
130
+ "name": "MCP_HTTP_ENDPOINT_PATH",
131
+ "description": "The endpoint path for the MCP server.",
132
+ "format": "string",
133
+ "isRequired": false,
134
+ "default": "/mcp"
135
+ },
136
+ {
137
+ "name": "MCP_PUBLIC_URL",
138
+ "description": "Public origin override for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).",
139
+ "format": "string",
140
+ "isRequired": false
141
+ },
142
+ {
143
+ "name": "MCP_AUTH_MODE",
144
+ "description": "Authentication mode to use: 'none', 'jwt', or 'oauth'.",
145
+ "format": "string",
146
+ "isRequired": false,
147
+ "default": "none"
148
+ },
149
+ {
150
+ "name": "MCP_LOG_LEVEL",
151
+ "description": "Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').",
152
+ "format": "string",
153
+ "isRequired": false,
154
+ "default": "info"
155
+ }
156
+ ],
157
+ "transport": {
158
+ "type": "streamable-http",
159
+ "url": "http://localhost:3010/mcp"
160
+ }
161
+ }
162
+ ]
163
+ }