Skip to main content
Glama
dylanpieper

RMI Utility Transition Hub — MCP Server

by dylanpieper
README.md
# MCP Server for RMI's Utility Transition Hub

This MCP server loads [RMI's public Utility Transition Hub data](https://utilitytransitionhub.rmi.org/data-download/) into DuckDB. Any MCP client can then query U.S. utility emissions, generation mix, and climate alignment with natural language.

> [!WARNING]
> **This is a demo. Use it at your own risk.**
>
> This software is not audited, validated, or ready for production. The tools can
> return errors. The tools can also aggregate the data incorrectly, and a language
> model can interpret the results incorrectly.
>
> Check every result against the [Utility Transition Hub](https://utilitytransitionhub.rmi.org/)
> and the RMI methodology before you cite it, publish it, or make a decision with
> it. RMI does not endorse this tool. This tool has no affiliation with RMI.

## Setup

### 1. Install the Dependencies

```bash
pip install fastmcp duckdb pandas pymupdf
```

Or use uv:

```bash
uv init rmi-mcp-uth
cd rmi-mcp-uth
uv add fastmcp duckdb pandas pymupdf
```

### 2. Download the RMI Data

Download the data from the [Utility Transition Hub](https://utilitytransitionhub.rmi.org/data-download/). Extract the data into a `data/` directory:

```bash
mkdir data
cd data

curl -LO https://utilitytransitionhub.rmi.org/static/data_download/data_download_all_pt_1.zip
curl -LO https://utilitytransitionhub.rmi.org/static/data_download/data_download_all_pt_2.zip

unzip -q -o -j data_download_all_pt_1.zip
unzip -q -o -j data_download_all_pt_2.zip

rm data_download_all_pt_1.zip data_download_all_pt_2.zip
```

### 3. Test With the MCP Inspector

```bash
npx @modelcontextprotocol/inspector python server.py
```

This command opens a browser interface. In the interface, you can call each tool, examine the resources, and monitor the protocol traffic.

### 4. Connect a Client

The server uses the stdio transport, and any MCP client can connect to it. See your provider's documentation or ask your agent (e.g., Claude Code) to set it up.

## What the Server Exposes

### Tools

| Tool | What it does |
|---|---|
| `list_tables` | Show all tables, columns, and row counts |
| `preview_table` | Show sample rows from a table |
| `list_utilities` | Find utilities by state or name. The best matches come first. |
| `get_emissions_trend` | Show CO2 emissions over time for a utility. `start_year` and `end_year` limit the range. |
| `get_generation_mix` | Show the generation breakdown by `technology_rmi`. `group_by="technology"` combines the subsidiaries of a parent into one fleet. `match_irp_entity=True` widens the match to every operating utility behind a joint IRP filing. |
| `get_climate_alignment` | Compare the actual CO2 to the 1.5°C pathway. `start_year` and `end_year` limit the range. |
| `rank_climate_alignment` | Rank utilities or parents by distance from the 1.5°C pathway. Excludes the utilities whose benchmark makes the comparison meaningless, and says which. `scope` picks the population: `"comparable"` (default), `"all"`, or `"flagged"` to inspect what was cut. |
| `query_data` | Run read-only SQL in DuckDB syntax |

### Response Format

Every tool returns the same envelope:

```json
{
  "rows":  [{"utility_name": "Wisconsin Power & Light Co.", "year": 2024,
             "owned_delivered": "delivered", "emissions_co2": 4.87}],
  "units": {"emissions_co2": "MMT CO2"},
  "meta":  {"grain": ["utility_name", "year", "owned_delivered"],
            "matched_utilities": ["Wisconsin Power & Light Co."]},
  "notes": [{"kind": "projection", "message": "Rows at or after projected_from are ..."}],
  "error": null
}
```

| Key | What it holds |
|---|---|
| `rows` | The data. Every row has the same keys. Warnings and errors never appear here. |
| `units` | The unit of each column that has one. Columns absent from it are unitless. |
| `meta` | `grain` lists the columns that together identify one row, so you can tell whether adding rows up double-counts. It is `null` where the rows have no key the server can promise — `query_data`, whose shape the caller wrote, and `preview_table`, which does not aggregate. Name searches add `matched_utilities`; emissions tools add `historical_through` and `projected_from`. |
| `notes` | Warnings that apply to the whole response. Each has a `kind`: `projection`, `excluded_by_default`, `not_comparable`, `below_min_emissions`, `entity_scope`, `truncated`. |
| `error` | `null`, or `{"message": ...}` plus fields naming how to narrow the request. When it is set, `rows` is empty. |

`grain` guards against counting the same row twice. Whether a *column* can be
added up at all is a separate question, answered by `meta.non_additive` — the
list of returned columns that must never be summed across rows. It is present
only when such a column is in the result.

Most entries are rates, and have to be recomputed from the two totals
underneath them rather than added: `co2_intensity_kg_mwh`, `capacity_factor`,
`pct_over`, `saidi`, `bill`, `burden`. But do not infer non-additivity from the
unit — `capacity_owned_in_state` is plain MW and still must not be summed,
because RMI's figure runs several times above the utility's ownership share and
adding it across a state's utilities exceeds that state's real capacity. Read
`non_additive`, not the unit string.

### Resources

| URI | Description |
|---|---|
| `rmi://data-dictionary` | A summary of the datasets, columns, and units |
| `rmi://data-dictionary-full` | The full data dictionary PDF. It gives the definitions, sources, and methodology for each field. |
| `rmi://methodology` | The RMI methodology document. It gives the data sources, assumptions, and calculations. |

### Prompts

| Prompt | Description |
|---|---|
| `decarbonization_assessment` | Analyze the climate progress of a utility |
| `state_landscape` | Show an overview of the utilities in a state |
| `investment_risk_profile` | Show the financial exposure of a utility to fossil fuel assets |

## Example Conversations

- "Which US utilities are furthest off the 1.5°C pathway?"
- "Which Wisconsin utilities have the highest CO2 emissions?"
- "Compare Alliant Energy and Xcel Energy's generation mix"
- "Show me Wisconsin Power & Light's emissions trend since 2015"
- "Is Alliant Energy on track for 1.5°C, or is WEC Energy Group closer?"
- "What share of Wisconsin generation comes from renewables?"

## Rebuilding the Database

Delete `utility_hub.duckdb` and restart the server. The server builds the database again from the CSV files.

## Data License

RMI licenses the Utility Transition Hub data under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation3/5

list_tables, preview_table, list_utilities, and get_generation_mix are clearly distinct, but get_emissions_trend and get_climate_alignment both promise yearly CO2 emissions versus a 1.5°C pathway. The detailed descriptions help, but an agent could easily pick the wrong one.

Naming Consistency4/5

Most tools follow a get_/list_ + noun pattern, which is predictable. preview_table and query_data deviate slightly, but they still read naturally and fit the overall exploration-oriented convention.

Tool Count5/5

Seven tools is well-scoped for a domain-specific utility data hub. Each tool has a clear role, and the count is neither bloated nor too thin.

Completeness5/5

The surface covers data discovery, table preview, utility lookup, standard analytical queries, and raw SQL for arbitrary exploration. There are no obvious dead ends for a read-only analysis server.

Maintenance

ActivityMaintained
ResponsivenessNo issues