mcp-server-eia
This server provides MCP tools that enable LLM agents to explore and query the U.S. Energy Information Administration (EIA) Open Data API. You can:
Browse the dataset tree with
eia_browse_routesto discover datasets across 17+ categories (electricity, natural gas, petroleum, coal, renewables, CO₂ emissions, nuclear outages, and energy outlooks like AEO/IEO/STEO) and retrieve metadata such as available frequencies, facets, valid data columns, and date ranges.Identify filterable facets using
eia_list_facets(e.g., state, sector, fuel type) for a given dataset.Get valid facet options with
eia_get_facet_optionsto ensure filters use real values.Query data via
eia_get_datawith column selection, facet-based filtering, frequency selection (annual, monthly, daily, hourly), date range, sorting, and offset pagination (up to 5,000 rows per request).
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-eiaShow me monthly electricity retail sales for California in 2023"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-eia
An MCP server that exposes the U.S. Energy Information Administration (EIA) Open Data API v2 to LLM clients. It provides a small set of generic, composable tools that mirror the API's uniform tree structure (browse → discover facets → query data), giving agents full coverage of all 17 EIA datasets — electricity, natural gas, petroleum, coal, nuclear outages, CO2 emissions, renewables, and the energy outlooks (AEO/IEO/STEO) — without hard-coding hundreds of endpoints.
Tools
Tool | Purpose |
| Explore the dataset tree from any path (empty = the 17 top-level datasets). At a leaf, returns dataset metadata: available frequencies, facet ids, valid data columns, and the covered date range. Primary discovery tool. |
| List the facet ids a dataset can be filtered by (e.g. |
| List the valid option values for a single facet, so filters use real ids. |
| Query dataset rows with column selection, facet filters, frequency, date range, sorting, and pagination. Returns structured JSON with pagination metadata. |
Related MCP server: ipums-mcp
How the EIA API is shaped
The API is a recursive tree. A GET on a route path returns either child
routes (an intermediate node) or leaf metadata (a queryable dataset). The
typical workflow is:
eia_browse_routes(route="")— list top-level datasets.Drill down, e.g.
eia_browse_routes(route="electricity/retail-sales")— read the validdata_columns,frequencies, and facet ids.eia_list_facets/eia_get_facet_options— find valid filter values.eia_get_data(...)— retrieve the numbers.
Conventions
Routes are slash paths without a
v2/prefix, e.g.electricity/retail-sales.Date formats depend on frequency:
2020(annual),2020-01(monthly),2020-01-01(daily),2020-01-01T00(hourly).Facets are passed as
{facet_id: [values]}; sort as[{"column": ..., "direction": "asc"|"desc"}].Pagination:
length(page size, max 5000) andoffset. Reuse thenext_offsetreturned by the previouseia_get_dataresponse.
Requirements
Python
>=3.14uvfor dependency managementA free EIA API key: https://www.eia.gov/opendata/register.php
Setup
uv syncAuthentication
Set the EIA_API_KEY environment variable, or create a .env file in the
project root (loaded automatically at startup; .env is gitignored):
EIA_API_KEY=your_key_hereRunning
uv run python -m eia_mcp.appWith no port env var set, the server runs over stdio (for Claude Desktop, Claude Code, and other local MCP clients).
If
PORTorDATABRICKS_APP_PORTis set, it runs over streamable HTTP on that port, serving MCP at the fixed path/mcpand aGET /healthreadiness endpoint. This is the mode the container image uses.
Container / gateway-hosted deployment
The included Dockerfile builds an image that serves MCP over streamable HTTP
at :8080/mcp (health at /health) — the contract the GSA Obot MCP gateway
expects for a containerized server.
docker build -t mcp-server-eia .
docker run --rm -p 8080:8080 -e EIA_API_KEY=your_key_here mcp-server-eia
curl -s localhost:8080/health # {"status":"healthy","service":"mcp-server-eia"}Publish a public, version-pinned image for the gateway to pull:
./scripts/build-and-push.sh # tags ghcr.io/gsa-tts/mcp-server-eia:<version>The gateway's Docker runtime pulls without registry auth, so the image must be publicly pullable. Set the GHCR package visibility to public after the first push.
Authentication model
This server deals with two distinct credentials on two different hops — do not conflate them:
Credential | Hop | Who supplies / enforces it |
Gateway/transport auth (e.g. Obot API key) | client → gateway → this server | The Obot gateway. In the |
| this server → | Read from the environment at call time ( |
Because the gateway owns transport auth and each user's key is isolated per
instance, the server intentionally sets no FastMCP auth provider — it
assumes zero transport-authentication responsibility.
If this server is ever deployed as a
remoteserver (a public URL reachable independently of the gateway), the MCP endpoint would be unauthenticated. In that case add a FastMCP server-sideJWTVerifiervalidating the gateway/SSO issuer (contingent on that issuer exposing a JWKS endpoint) — notOAuthProxy/OAuthProvider. Keeping the servercontainerized(gateway-guarded) avoids this.
Example MCP client config (stdio)
{
"mcpServers": {
"eia": {
"command": "uv",
"args": ["run", "python", "-m", "eia_mcp.app"],
"cwd": "/path/to/mcp-server-eia",
"env": { "EIA_API_KEY": "your_key_here" }
}
}
}Project layout
src/eia_mcp/
├── app.py # FastMCP init, instructions, transport selection
├── routes.py # HTTP health-check route
├── utils.py # API key, URL building, param encoding, HTTP client, errors
└── tools/
├── __init__.py # register_tools(mcp): wires up all tools
├── browse_routes.py # eia_browse_routes
├── list_facets.py # eia_list_facets
├── get_facet_options.py # eia_get_facet_options
└── get_data.py # eia_get_data
Dockerfile # containerized deployment (:8080/mcp, /health)
scripts/build-and-push.sh # build + push public GHCR image for the gateway
docs/eia-api-swagger/ # EIA API v2 OpenAPI/Swagger referenceEach tool lives in its own file and exposes a register(mcp) function; new tools
are added by dropping a file in tools/ and registering it in
tools/__init__.py.
Data source & attribution
Data is retrieved live from the U.S. Energy Information Administration Open Data API. See the EIA API terms of service for usage and attribution requirements.
Available Tools
4 toolseia_browse_routesBrowse EIA routes / dataset metadataARead-only
Explore the EIA dataset tree, or fetch a leaf dataset's metadata.
Behavior depends on the node type at route:
Intermediate node -> returns
{"type": "routes", "routes": [...]}, each item being a selectable child route id + name/description.Leaf dataset -> returns
{"type": "dataset", ...}with the availablefrequencies,facets(facet ids to filter on), validdatacolumns, and thestartPeriod/endPerioddate coverage. Feed these into eia_get_data.
Start with route="" to discover datasets, then drill down. This is the primary discovery tool; call it before eia_get_data to learn the valid columns, facet ids, and frequency for a dataset.
| Name | Required | Description | Default |
|---|---|---|---|
| route | No | Dataset route path to inspect. Use an empty string to list the top-level datasets (e.g. electricity, natural-gas, petroleum, coal, total-energy). Drill down by appending child ids, e.g. 'electricity' then 'electricity/retail-sales'. Leading/trailing slashes and a 'v2/' prefix are tolerated. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide readOnlyHint=true and openWorldHint=true, but the description adds valuable behavioral details: behavior depends on node type, returns different response structures, and tolerates leading/trailing slashes and 'v2/' prefix. It does not contradict annotations. Minor deduction because it doesn't discuss rate limits or error handling, but the added context is substantive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: a one-sentence purpose, a bulleted breakdown of node-type behavior, and a final usage directive. Every sentence earns its place, and the most important guidance is front-loaded. It is detailed but not bloated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present (has_output_schema=true), the description correctly avoids explaining raw return fields. It explains the two response types, how to interpret them, and how to feed results into eia_get_data. This is complete for the tool's complexity and context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for the single 'route' parameter, and the schema already documents the tolerated slash/prefix behavior and examples. The tool description reinforces this but doesn't add meaning beyond the schema. Baseline 3 is appropriate because the schema carries the parameter documentation burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it 'Explores the EIA dataset tree, or fetches a leaf dataset's metadata' with a specific verb and resource. It distinguishes itself from sibling tools by explicitly calling itself 'the primary discovery tool' and referencing eia_get_data, making its role in the workflow unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use guidance: 'Start with route="" to discover datasets, then drill down. This is the primary discovery tool; call it before eia_get_data...' It also explains the two node types and what the agent should do with each response, making the usage context very clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
eia_get_dataQuery EIA dataset dataARead-only
Query rows from an EIA dataset with filtering, sorting, and paging.
Returns a structured payload: { "route", "frequency", "description", "total": , "offset", "length", "returned": , "has_more": , "next_offset": <int | None>, "data": [ {row}, ... ], "warnings": [ ... ] # present only when relevant }
Workflow: use eia_browse_routes to find the route, its valid
data_columns, frequencies, and facet ids; use eia_get_facet_options
for valid facet values; then call this tool. If data columns are wrong
the API may return empty rows, so verify columns against the metadata.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | Inclusive end period, same format as `start`. | |
| data | Yes | Data columns to return, e.g. ['revenue', 'sales', 'price']. Valid columns are listed in the dataset metadata from eia_browse_routes (the 'data_columns' field). At least one column is usually required for the API to return values. | |
| sort | No | Sort spec, e.g. [{'column': 'period', 'direction': 'desc'}]. 'direction' is 'asc' or 'desc'. | |
| route | Yes | Leaf dataset route to query, e.g. 'electricity/retail-sales'. Discover with eia_browse_routes. | |
| start | No | Inclusive start period. Format must match the dataset frequency, e.g. '2020' (annual), '2020-01' (monthly), '2020-01-01' (daily), '2020-01-01T00' (hourly). | |
| facets | No | Facet filters as {facet_id: [values]}, e.g. {'stateid': ['CA'], 'sectorid': ['RES']}. Discover facet ids with eia_list_facets and valid values with eia_get_facet_options. | |
| length | No | Max rows to return (page size). 1..5000. Keep small for exploration; increase to page through data. | |
| offset | No | Row offset for pagination. Use `next_offset` from a prior call. | |
| frequency | No | Data frequency id, e.g. 'monthly', 'annual', 'hourly'. Valid values are in the dataset 'frequencies' metadata. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description details the exact response structure (route, frequency, total, offset, has_more, next_offset, data, warnings) and explains pagination behavior via next_offset. It also discloses a behavioral quirk: incorrect data columns may yield empty rows. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: a one-sentence summary, a compact response payload block, and a brief workflow. Every sentence adds value, including the caveat about data columns. It is appropriately sized for a 9-parameter tool with pagination and facets, and it front-loads the core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (9 parameters, facets, frequencies, pagination), the description covers the essential context: how to obtain valid parameters via sibling tools, the response format, pagination using next_offset, and a key failure mode. The presence of an output schema description further supports completeness. No significant gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema describes all parameters well. The description adds workflow-level semantics: connecting route to data_columns, frequencies, and facet ids, and explaining that invalid data columns can cause empty results. This extra context goes beyond the schema's per-parameter descriptions, though the schema still carries the bulk of the meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Query rows from an EIA dataset with filtering, sorting, and paging,' which clearly states the verb (query), resource (EIA dataset), and operations (filtering, sorting, paging). It distinguishes this tool from siblings like eia_browse_routes (route discovery) and eia_get_facet_options (facet values) by focusing on data retrieval.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The workflow section explicitly instructs using eia_browse_routes to find the route and metadata, then eia_get_facet_options for facet values, then 'call this tool.' It also warns about potential empty rows if data columns are incorrect. This provides clear context and alternatives, though it doesn't explicitly state 'do not use this tool for discovery' or list exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
eia_get_facet_optionsGet EIA facet option valuesARead-only
List the valid option values (ids) for one facet of a dataset.
Returns {"route", "facet_id", "total", "options": [...]}. Use the
returned ids as values in the facets argument of eia_get_data, e.g.
facets={"stateid": ["CA", "NY"]}.
| Name | Required | Description | Default |
|---|---|---|---|
| route | Yes | Leaf dataset route, e.g. 'electricity/retail-sales'. | |
| facet_id | Yes | Facet id whose values to list, e.g. 'stateid' or 'sectorid'. Discover facet ids with eia_list_facets. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint. The description adds the exact return format (`{route, facet_id, total, options: [...]}`) and a concrete usage example. This goes beyond the annotations and helps the agent anticipate results and next steps. No contradictions found.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences plus a code block. It is front-loaded with the core purpose, then immediately provides return format and usage guidance. No filler or redundant content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple (2 parameters), the schema fully documents parameters, and the description explains the return format and how to apply the results. The output schema exists (though not shown), and the description fills in the key integration detail with eia_get_data. The only minor gap is not clarifying the `openWorldHint` implications, but this is a small miss given the overall clarity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with detailed descriptions for both `route` and `facet_id`, including examples. The description does not add new parameter-level information beyond what the schema provides, so the baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's action: 'List the valid option values (ids) for one facet of a dataset.' This uses a specific verb ('list'), names the resource ('facet options'), and is distinct from sibling tools like browse_routes, list_facets, and get_data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly explains how to use the returned ids: 'Use the returned ids as values in the `facets` argument of eia_get_data' with an example. This gives clear context for when to use the tool. It does not explicitly exclude alternatives or mention when not to use it, but the usage scenario is well defined.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
eia_list_facetsList EIA dataset facetsARead-only
List the facet ids you can filter a dataset by.
Returns {"route": ..., "facets": [{"id", "description"}, ...]}. Pass a
facet id to eia_get_facet_options to enumerate its valid values, then use
those values in the facets argument of eia_get_data.
| Name | Required | Description | Default |
|---|---|---|---|
| route | Yes | Leaf dataset route to list facets for, e.g. 'electricity/retail-sales'. Discover routes with eia_browse_routes. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and open-world, so the safety profile is covered. The description adds the exact return structure ({"route", "facets"}) and clarifies that it is a discovery step in a multi-tool flow. While it doesn't discuss edge cases or failure modes, it provides useful behavioral context beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences plus an inline return-type example. It's front-loaded with the core purpose, then gives a concise workflow. Every sentence earns its place; there is no fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete for this simple one-parameter tool. It states the purpose, provides the return format (which is also reflected in the output schema), and explains the relationship with sibling tools. Combined with the schema and annotations, an agent has everything needed to invoke this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema fully describes the route parameter with an example and a pointer to eia_browse_routes, so schema coverage is 100%. The description itself does not add parameter-level meaning beyond what the schema already offers; it focuses on the workflow. Given the complete schema, a baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description begins with a specific verb+resource: 'List the facet ids you can filter a dataset by.' This clearly distinguishes the tool from sibling tools like eia_browse_routes (listing routes), eia_get_facet_options (enumerating values for a facet), and eia_get_data (fetching data). The purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides an explicit workflow: after listing facets, pass a facet id to eia_get_facet_options, then use the values in eia_get_data. It also references eia_browse_routes for route discovery in the parameter description. This is concrete, actionable guidance on how and when to use the tool within the larger API.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
4 tool updates
v0.1.0- First observed
eia_browse_routes - First observed
eia_get_data - First observed
eia_get_facet_options - First observed
eia_list_facets
TDQS
Each tool has a clear role: browse_routes discovers datasets and metadata, list_facets enumerates facets, get_facet_options lists facet values, and get_data queries rows. There is minor overlap between browse_routes and list_facets since browse_routes already returns facet IDs, but the descriptions clarify when to use each.
All tools follow the consistent eia_<verb>_<noun> pattern with snake_case. The verbs 'browse', 'list', and 'get' are used predictably across the set, making it easy to infer functionality from the name.
With only 4 tools, the server is well-scoped for its purpose. Each tool addresses a distinct step in the EIA data access workflow: discovery, metadata, facet enumeration, and data retrieval, without unnecessary bloat.
The tool set covers the full workflow from discovering routes to fetching filtered data with paging. A minor gap is that there is no direct tool to list data columns or frequencies without calling browse_routes, but that metadata is returned in browse_routes, so it's workable.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Protocol-native energy infrastructure orchestration for AI data centers. Provides 46 MCP tools across 8 grid protocols (IEC-61850, DNP3, Modbus, OCPP, OpenADR, IEEE 2030.5, IEC 60870-5-104, ICCP) with 5 core API primitives: connect, dispatch, settle, comply, and intel. Enables AI agents to programmatically interact with substations, grid interfaces, and energy assets for real-time workload-grid coordination.
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
MCP server that lets AI assistants use all OneSchema features exposed via the public API.
MCP server exposing the Backtest360 engine API as tools for AI agents.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to operate a US power-grid data pipeline – ingest, plan, run, and monitor datasets like CAISO, ERCOT, and EIA-930 through MCP tools that mirror the CLI.Apache 2.0
- AlicenseAqualityDmaintenanceAn MCP server that exposes the IPUMS API as LLM tools for browsing metadata, creating and downloading extracts, and generating reproducible R/Python code.23MIT
- FlicenseNot gradedqualityCmaintenanceA FastMCP server that wraps the U.S. Energy Information Administration Open Data API v2, enabling natural-language queries for electricity, petroleum, and other energy statistics via tools like discover_eia_route and get_eia_data.-
- AlicenseNot gradedqualityCmaintenanceAn MCP server that wraps the U.S. Energy Information Administration's Open Data API, enabling assistants to fetch live energy data via natural language. It provides tools for querying series, browsing data routes, filtering facets, and running custom queries.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/GSA-TTS/mcp-server-eia'
If you have feedback or need assistance with the MCP directory API, please join our Discord server