Skip to main content
Glama
GSA-TTS

mcp-server-eia

by GSA-TTS

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

eia_browse_routes

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.

eia_list_facets

List the facet ids a dataset can be filtered by (e.g. stateid, sectorid, fueltypeid).

eia_get_facet_options

List the valid option values for a single facet, so filters use real ids.

eia_get_data

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:

  1. eia_browse_routes(route="") — list top-level datasets.

  2. Drill down, e.g. eia_browse_routes(route="electricity/retail-sales") — read the valid data_columns, frequencies, and facet ids.

  3. eia_list_facets / eia_get_facet_options — find valid filter values.

  4. 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) and offset. Reuse the next_offset returned by the previous eia_get_data response.

Requirements

Setup

uv sync

Authentication

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_here

Running

uv run python -m eia_mcp.app
  • With no port env var set, the server runs over stdio (for Claude Desktop, Claude Code, and other local MCP clients).

  • If PORT or DATABRICKS_APP_PORT is set, it runs over streamable HTTP on that port, serving MCP at the fixed path /mcp and a GET /health readiness 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 containerized deployment the container has no public route, so the gateway is the only caller and it enforces access.

EIA_API_KEY

this server → api.eia.gov

Read from the environment at call time (utils.get_api_key). In a singleUser gateway deployment, each user gets their own container instance with their own key injected as an env var.

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 remote server (a public URL reachable independently of the gateway), the MCP endpoint would be unauthenticated. In that case add a FastMCP server-side JWTVerifier validating the gateway/SSO issuer (contingent on that issuer exposing a JWKS endpoint) — not OAuthProxy/OAuthProvider. Keeping the server containerized (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 reference

Each 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 tools
eia_browse_routesBrowse EIA routes / dataset metadataA
Read-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 available frequencies, facets (facet ids to filter on), valid data columns, and the startPeriod/endPeriod date 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
routeNoDataset 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

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 dataA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
endNoInclusive end period, same format as `start`.
dataYesData 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.
sortNoSort spec, e.g. [{'column': 'period', 'direction': 'desc'}]. 'direction' is 'asc' or 'desc'.
routeYesLeaf dataset route to query, e.g. 'electricity/retail-sales'. Discover with eia_browse_routes.
startNoInclusive start period. Format must match the dataset frequency, e.g. '2020' (annual), '2020-01' (monthly), '2020-01-01' (daily), '2020-01-01T00' (hourly).
facetsNoFacet 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.
lengthNoMax rows to return (page size). 1..5000. Keep small for exploration; increase to page through data.
offsetNoRow offset for pagination. Use `next_offset` from a prior call.
frequencyNoData frequency id, e.g. 'monthly', 'annual', 'hourly'. Valid values are in the dataset 'frequencies' metadata.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 valuesA
Read-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"]}.

ParametersJSON Schema
NameRequiredDescriptionDefault
routeYesLeaf dataset route, e.g. 'electricity/retail-sales'.
facet_idYesFacet id whose values to list, e.g. 'stateid' or 'sectorid'. Discover facet ids with eia_list_facets.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 facetsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
routeYesLeaf dataset route to list facets for, e.g. 'electricity/retail-sales'. Discover routes with eia_browse_routes.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 4 tool updatesv0.1.0
    • First observedeia_browse_routes
    • First observedeia_get_data
    • First observedeia_get_facet_options
    • First observedeia_list_facets

TDQS

A4.4/5.0
Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

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

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that exposes the IPUMS API as LLM tools for browsing metadata, creating and downloading extracts, and generating reproducible R/Python code.
    23
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A 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.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    An 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

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