aeso-mcp 0.2.0__tar.gz

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 (62) hide show
  1. aeso_mcp-0.2.0/LICENSE +21 -0
  2. aeso_mcp-0.2.0/PKG-INFO +325 -0
  3. aeso_mcp-0.2.0/README.md +289 -0
  4. aeso_mcp-0.2.0/pyproject.toml +144 -0
  5. aeso_mcp-0.2.0/pyproject.toml.orig +137 -0
  6. aeso_mcp-0.2.0/src/aeso_mcp/__init__.py +7 -0
  7. aeso_mcp-0.2.0/src/aeso_mcp/__main__.py +89 -0
  8. aeso_mcp-0.2.0/src/aeso_mcp/app.py +81 -0
  9. aeso_mcp-0.2.0/src/aeso_mcp/config.py +341 -0
  10. aeso_mcp-0.2.0/src/aeso_mcp/data/__init__.py +2 -0
  11. aeso_mcp-0.2.0/src/aeso_mcp/data/glossary.md +54 -0
  12. aeso_mcp-0.2.0/src/aeso_mcp/errors.py +58 -0
  13. aeso_mcp-0.2.0/src/aeso_mcp/http_runtime.py +375 -0
  14. aeso_mcp-0.2.0/src/aeso_mcp/mcp/__init__.py +6 -0
  15. aeso_mcp-0.2.0/src/aeso_mcp/mcp/errors.py +57 -0
  16. aeso_mcp-0.2.0/src/aeso_mcp/mcp/prompts/__init__.py +18 -0
  17. aeso_mcp-0.2.0/src/aeso_mcp/mcp/prompts/market.py +102 -0
  18. aeso_mcp-0.2.0/src/aeso_mcp/mcp/resources/__init__.py +22 -0
  19. aeso_mcp-0.2.0/src/aeso_mcp/mcp/resources/capabilities.py +88 -0
  20. aeso_mcp-0.2.0/src/aeso_mcp/mcp/resources/datasets.py +116 -0
  21. aeso_mcp-0.2.0/src/aeso_mcp/mcp/resources/glossary.py +39 -0
  22. aeso_mcp-0.2.0/src/aeso_mcp/mcp/resources/methodology.py +206 -0
  23. aeso_mcp-0.2.0/src/aeso_mcp/mcp/server.py +71 -0
  24. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/__init__.py +14 -0
  25. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/analytics.py +106 -0
  26. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/grid.py +141 -0
  27. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/market.py +123 -0
  28. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/market_power.py +63 -0
  29. aeso_mcp-0.2.0/src/aeso_mcp/mcp/tools/operations.py +189 -0
  30. aeso_mcp-0.2.0/src/aeso_mcp/models/__init__.py +106 -0
  31. aeso_mcp-0.2.0/src/aeso_mcp/models/analytics.py +172 -0
  32. aeso_mcp-0.2.0/src/aeso_mcp/models/assets.py +43 -0
  33. aeso_mcp-0.2.0/src/aeso_mcp/models/common.py +125 -0
  34. aeso_mcp-0.2.0/src/aeso_mcp/models/generation.py +100 -0
  35. aeso_mcp-0.2.0/src/aeso_mcp/models/grid.py +99 -0
  36. aeso_mcp-0.2.0/src/aeso_mcp/models/market_power.py +72 -0
  37. aeso_mcp-0.2.0/src/aeso_mcp/models/operations.py +325 -0
  38. aeso_mcp-0.2.0/src/aeso_mcp/models/prices.py +67 -0
  39. aeso_mcp-0.2.0/src/aeso_mcp/models/transmission.py +78 -0
  40. aeso_mcp-0.2.0/src/aeso_mcp/providers/__init__.py +18 -0
  41. aeso_mcp-0.2.0/src/aeso_mcp/providers/aeso_apim.py +301 -0
  42. aeso_mcp-0.2.0/src/aeso_mcp/providers/base.py +76 -0
  43. aeso_mcp-0.2.0/src/aeso_mcp/providers/capabilities.py +91 -0
  44. aeso_mcp-0.2.0/src/aeso_mcp/providers/csd.py +92 -0
  45. aeso_mcp-0.2.0/src/aeso_mcp/providers/gridstatus.py +669 -0
  46. aeso_mcp-0.2.0/src/aeso_mcp/providers/http.py +202 -0
  47. aeso_mcp-0.2.0/src/aeso_mcp/providers/operations.py +564 -0
  48. aeso_mcp-0.2.0/src/aeso_mcp/providers/public_reports.py +527 -0
  49. aeso_mcp-0.2.0/src/aeso_mcp/providers/public_reports_http.py +168 -0
  50. aeso_mcp-0.2.0/src/aeso_mcp/py.typed +1 -0
  51. aeso_mcp-0.2.0/src/aeso_mcp/server.py +6 -0
  52. aeso_mcp-0.2.0/src/aeso_mcp/services/__init__.py +18 -0
  53. aeso_mcp-0.2.0/src/aeso_mcp/services/analytics.py +540 -0
  54. aeso_mcp-0.2.0/src/aeso_mcp/services/assets.py +64 -0
  55. aeso_mcp-0.2.0/src/aeso_mcp/services/cache.py +220 -0
  56. aeso_mcp-0.2.0/src/aeso_mcp/services/grid.py +138 -0
  57. aeso_mcp-0.2.0/src/aeso_mcp/services/market.py +646 -0
  58. aeso_mcp-0.2.0/src/aeso_mcp/services/market_power.py +136 -0
  59. aeso_mcp-0.2.0/src/aeso_mcp/services/operations.py +875 -0
  60. aeso_mcp-0.2.0/src/aeso_mcp/services/transmission.py +128 -0
  61. aeso_mcp-0.2.0/src/aeso_mcp/services/ttl.py +29 -0
  62. aeso_mcp-0.2.0/src/aeso_mcp/timeutil.py +206 -0
aeso_mcp-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brandon Choi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,325 @@
1
+ Metadata-Version: 2.4
2
+ Name: aeso-mcp
3
+ Version: 0.2.0
4
+ Summary: Agent-native MCP server for Alberta electricity market data via official AESO APIs
5
+ Keywords: aeso,alberta,electricity,energy,mcp,model-context-protocol,grid
6
+ Author: Brandon Choi
7
+ Author-email: Brandon Choi <bchoi0824@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: beautifulsoup4>=4.12.0,<5
18
+ Requires-Dist: fastmcp==4.0.0b3
19
+ Requires-Dist: gridstatus>=0.36.0,<0.37
20
+ Requires-Dist: httpx>=0.28.1,<1
21
+ Requires-Dist: pandas>=2.2.0,<3
22
+ Requires-Dist: pydantic>=2.11.0,<3
23
+ Requires-Dist: pydantic-settings>=2.8.0,<3
24
+ Requires-Dist: tenacity>=9.0.0,<10
25
+ Requires-Dist: tzdata>=2025.1
26
+ Requires-Dist: duckdb>=1.2.0,<2 ; extra == 'analytics'
27
+ Requires-Dist: pyarrow>=19.0.0,<22 ; extra == 'analytics'
28
+ Requires-Python: >=3.13
29
+ Project-URL: Homepage, https://github.com/bchoi-qwe/aeso-mcp
30
+ Project-URL: Documentation, https://github.com/bchoi-qwe/aeso-mcp#readme
31
+ Project-URL: Repository, https://github.com/bchoi-qwe/aeso-mcp
32
+ Project-URL: Issues, https://github.com/bchoi-qwe/aeso-mcp/issues
33
+ Project-URL: Changelog, https://github.com/bchoi-qwe/aeso-mcp/blob/main/CHANGELOG.md
34
+ Provides-Extra: analytics
35
+ Description-Content-Type: text/markdown
36
+
37
+ # aeso-mcp
38
+
39
+ <!-- mcp-name: io.github.bchoi-qwe/aeso-mcp -->
40
+
41
+ **Agent-native, strongly typed access and analytics for Alberta's electricity market using official AESO data.**
42
+
43
+ > Independent open-source project. **Not affiliated with or endorsed by the Alberta Electric System Operator (AESO).**
44
+
45
+ ## What it is
46
+
47
+ `aeso-mcp` is a Model Context Protocol (MCP) server that exposes Alberta electricity-market observations and deterministic analytics to AI clients. It is designed for energy analysts, researchers, developers, journalists, market participants, and AI agents that need reliable, structured AESO data—not a thin REST decorator layer.
48
+
49
+ ## Features
50
+
51
+ - Typed MCP tools with Pydantic inputs/outputs and structured results
52
+ - Current market snapshot combining price, load, generation, interchange, and reserves
53
+ - Paginated historical Pool Price, System Marginal Price, load, and generation retrieval
54
+ - Authenticated APIM reports for merit order, commitments, capability/outages, interties,
55
+ metered volumes, and operating-reserve offer control
56
+ - Deterministic analytics: compact history summaries, period comparison, event detection,
57
+ condition evidence, forecast accuracy, and transparent supply-tightness indicators
58
+ - One complete server package and startup path: `AESO_API_KEY` is always required; there is no
59
+ reduced credential-free server mode
60
+ - Query bounds, cache provenance, completeness metadata, upstream `Retry-After` handling, and
61
+ secret-safe machine-readable errors
62
+ - Hardened HTTP transport with Host/Origin validation, optional bearer authentication, rate and
63
+ concurrency limits, request-size bounds, probes, and correlation IDs
64
+ - Reusable MCP prompts plus glossary, capability, dataset, and methodology resources
65
+
66
+ ## Implemented datasets
67
+
68
+ | Dataset | Tool | Notes |
69
+ | --- | --- | --- |
70
+ | Market snapshot | `get_market_snapshot` | Current cohesive view |
71
+ | Pool Price | `get_pool_prices` | Hourly CAD/MWh |
72
+ | System Marginal Price | `get_system_marginal_prices` | Minute-level CAD/MWh |
73
+ | Alberta Internal Load | `get_load` | MW; optional forecast |
74
+ | Generation / fuel mix | `get_generation` | Current all fuels; historical wind/solar |
75
+ | Interchange | `get_interchange` | Current path flows MW |
76
+ | Operating reserves | `get_reserves` | Current MW indicators |
77
+ | Generator outages | `get_outages` | Hourly outage capacity by fuel/technology |
78
+ | AIES capacity/outages | `get_generation_capacity` | Hourly MC, AC, operating, and mothball outage MW |
79
+ | Load outage forecast | `get_load_outage_forecast` | Hourly forecast MW |
80
+ | Energy Merit Order | `get_energy_merit_order` | Historical blocks; 60-day publication delay |
81
+ | Unit commitments | `get_unit_commitments` | Generating-unit commitment directives |
82
+ | Intertie capability | `get_intertie_capability` | Import/export ATC, TTC, margins, gross offers |
83
+ | Intertie capability outages | `get_intertie_outages` | Outages affecting interties/flowgates |
84
+ | Metered volumes | `get_metered_volumes` | Hourly MWh by asset; optional ID filters |
85
+ | OR offer control | `get_operating_reserve_offer_control` | Historical reserve offer blocks; 60-day delay |
86
+ | Approved Tx outages | `get_approved_transmission_outages` | AESO-approved planned transmission outages |
87
+ | Long-range Tx outages | `get_long_range_transmission_outages` | Tentative ~24-month significant outages |
88
+ | MCSINR | `get_monthly_cumulative_net_revenue` | Cumulative net revenue vs offer-cap trigger |
89
+ | Secondary offer limit | `get_secondary_offer_price_limit` | Whether secondary offer cap is in effect |
90
+ | Assets | `get_assets` | Registry with filters |
91
+
92
+ Analytics: `summarize_market_history`, `assess_supply_tightness`, `compare_market_periods`,
93
+ `find_price_events`, `explain_market_conditions`, and `compare_forecast_to_actual`.
94
+
95
+ ## Architecture
96
+
97
+ ```text
98
+ MCP clients
99
+ |
100
+ v
101
+ FastMCP adapter (aeso_mcp/mcp)
102
+ |
103
+ v
104
+ Domain services (market, grid, assets, operations, analytics, transmission, market power)
105
+ |
106
+ +---------------------+------------------------+
107
+ | | |
108
+ v v v
109
+ GridStatus provider Direct AESO APIM Public-reports client
110
+ | | |
111
+ +----------+----------+ |
112
+ | |
113
+ v v
114
+ AESO APIM gateway ets.aeso.ca
115
+ ```
116
+
117
+ Domain code does not depend on FastMCP. Framework changes should stay in `aeso_mcp/mcp/`.
118
+ Both upstream clients are implementation details of this single server: the APIM key is sent only
119
+ to `apimgw.aeso.ca` and is never sent to the allow-listed `ets.aeso.ca` report host.
120
+
121
+ ## Requirements
122
+
123
+ - Python 3.13+
124
+ - AESO APIM API key from [developer-apim.aeso.ca](https://developer-apim.aeso.ca/)
125
+ - [`uv`](https://docs.astral.sh/uv/) recommended
126
+
127
+ ## Installation
128
+
129
+ See [LIMITATIONS.md](LIMITATIONS.md) for an honest gap inventory.
130
+
131
+ ### From PyPI (recommended)
132
+
133
+ ```bash
134
+ export AESO_API_KEY=your-key
135
+ uvx aeso-mcp
136
+ ```
137
+
138
+ ### From GitHub
139
+
140
+ To run the current repository version directly:
141
+
142
+ ```bash
143
+ export AESO_API_KEY=your-key
144
+ uvx --from git+https://github.com/bchoi-qwe/aeso-mcp.git aeso-mcp
145
+ ```
146
+
147
+ ### Development
148
+
149
+ ```bash
150
+ git clone https://github.com/bchoi-qwe/aeso-mcp.git
151
+ cd aeso-mcp
152
+ uv sync --group dev
153
+ cp .env.example .env # set AESO_API_KEY
154
+ uv run aeso-mcp
155
+ ```
156
+
157
+ ### Docker
158
+
159
+ ```bash
160
+ docker build -t aeso-mcp .
161
+ docker run --rm -e AESO_API_KEY=your-key -p 8000:8000 aeso-mcp
162
+ ```
163
+
164
+ ## Obtaining an AESO API key
165
+
166
+ 1. Register at the [AESO developer portal](https://developer-apim.aeso.ca/)
167
+ 2. Subscribe to the AESO public API product
168
+ 3. Copy the primary/secondary subscription key
169
+ 4. Set `AESO_API_KEY` in your environment (never commit it)
170
+
171
+ Missing credentials produce an actionable startup error. The key is never returned through MCP tools or logged.
172
+
173
+ ## Example MCP client configuration
174
+
175
+ ### Cursor / Claude Desktop style (stdio)
176
+
177
+ ```json
178
+ {
179
+ "mcpServers": {
180
+ "aeso": {
181
+ "command": "uvx",
182
+ "args": ["aeso-mcp"],
183
+ "env": {
184
+ "AESO_API_KEY": "your-key"
185
+ }
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ ### HTTP transport
192
+
193
+ ```bash
194
+ uv run aeso-mcp --transport http --host 127.0.0.1 --port 8000
195
+ ```
196
+
197
+ HTTP always validates Host and Origin. For a remotely reachable deployment, explicitly set
198
+ `AESO_MCP_HTTP_ALLOWED_HOSTS` and `AESO_MCP_HTTP_ALLOWED_ORIGINS`; set
199
+ `AESO_MCP_HTTP_BEARER_TOKEN` to require bearer authentication. `/healthz` and `/readyz` contain no
200
+ market data or secrets. See [.env.example](.env.example) for all bounded runtime settings.
201
+
202
+ ## Example prompts
203
+
204
+ - What is Alberta's current grid situation?
205
+ - What is the current pool price?
206
+ - Show Alberta pool prices over the last 24 hours.
207
+ - Compare today's pool prices with yesterday's.
208
+ - Which hours had the highest prices this week?
209
+ - How much wind and solar are producing right now?
210
+ - What happened during the largest price spike this week?
211
+ - Explain the evidence associated with today's price increase.
212
+
213
+ ## Tools
214
+
215
+ | Tool | Purpose |
216
+ | --- | --- |
217
+ | `get_market_snapshot` | Current market overview |
218
+ | `get_pool_prices` | Hourly Pool Price history |
219
+ | `get_system_marginal_prices` | Minute-level SMP history |
220
+ | `get_load` | Alberta Internal Load |
221
+ | `get_generation` | Fuel mix / renewable history |
222
+ | `get_interchange` | Intertie flows |
223
+ | `get_reserves` | Operating reserve indicators |
224
+ | `get_outages` | Hourly generator outage capacity by fuel |
225
+ | `get_generation_capacity` | AIES capability and outage grouping by fuel |
226
+ | `get_load_outage_forecast` | Hourly load-outage forecast |
227
+ | `get_energy_merit_order` | Historical energy merit-order blocks |
228
+ | `get_unit_commitments` | Generating-unit commitment directives |
229
+ | `get_intertie_capability` | Intertie/flowgate ATC, TTC, and margins |
230
+ | `get_intertie_outages` | Outages affecting intertie capability |
231
+ | `get_metered_volumes` | Metered energy by asset |
232
+ | `get_operating_reserve_offer_control` | Historical reserve offer-control blocks |
233
+ | `get_approved_transmission_outages` | Approved planned transmission outages |
234
+ | `get_long_range_transmission_outages` | Tentative long-range transmission outages |
235
+ | `get_assets` | Asset registry |
236
+ | `get_monthly_cumulative_net_revenue` | Current MCSINR publication |
237
+ | `get_secondary_offer_price_limit` | Current secondary offer-cap status |
238
+ | `compare_market_periods` | Aggregate period comparison |
239
+ | `find_price_events` | High-price event detection |
240
+ | `explain_market_conditions` | Structured evidence (not causal prose) |
241
+ | `compare_forecast_to_actual` | AIL forecast vs actual accuracy |
242
+ | `summarize_market_history` | Compact hourly/daily/weekly/monthly price and load summaries |
243
+ | `assess_supply_tightness` | Transparent reserve-adjusted supply-margin screening |
244
+
245
+ All tools are read-only, non-destructive, and network-dependent.
246
+
247
+ ## Resources
248
+
249
+ | URI | Content |
250
+ | --- | --- |
251
+ | `aeso://glossary` | Market terminology |
252
+ | `aeso://datasets` | Dataset catalog |
253
+ | `aeso://methodology/pool-price` | Pool Price interpretation |
254
+ | `aeso://methodology/system-marginal-price` | SMP interpretation |
255
+ | `aeso://capabilities` | Complete tool, prompt, and resource surface |
256
+ | `aeso://methodology/{dataset}` | Dataset-specific interpretation and caveats |
257
+
258
+ Prompts: `daily_market_brief`, `investigate_price_event`, and `compare_market_days`.
259
+
260
+ ## Data semantics
261
+
262
+ - **Timezone**: `America/Edmonton` (AESO market time). DST days may have 23 or 25 local hours.
263
+ - **Intervals**: Explicit `interval_start` / `interval_end` (half-open ranges in requests).
264
+ - **Units**: Pool Price / SMP → CAD/MWh; load / generation / interchange / reserves → MW.
265
+ - **Status**: Metadata includes `actual` / `forecast` / etc. Forecasts are never implied to be settled actuals.
266
+ - **Finality**: Operational feeds may be preliminary; do not assume final settlement.
267
+ - **Completeness**: Metadata reports available/missing series and expected/missing observations
268
+ where the source cadence is known. Optional enrichment failures are surfaced as partial or
269
+ degraded results with warnings.
270
+ - **Pagination**: Raw price, SMP, load, generation, and operational reports return `page` metadata
271
+ with `next_offset`. Use `summarize_market_history` before retrieving long raw series.
272
+ - **Cache timing**: `retrieved_at` identifies the upstream fetch; `served_at`, `cache_hit`, and
273
+ `cache_age` identify when and how the response was served.
274
+
275
+ ## Development
276
+
277
+ ```bash
278
+ uv sync --group dev
279
+ uv run ruff check src tests
280
+ uv run pyright src
281
+ uv run pytest tests/unit tests/contract tests/mcp --cov=aeso_mcp
282
+ uv build
283
+ ```
284
+
285
+ Optional live tests:
286
+
287
+ ```bash
288
+ AESO_API_KEY=... uv run pytest tests/integration -m integration
289
+ ```
290
+
291
+ MCP Inspector:
292
+
293
+ ```bash
294
+ # Prefer the console entrypoint; or point Inspector at:
295
+ # uv run aeso-mcp
296
+ npx @modelcontextprotocol/inspector uv run aeso-mcp
297
+ ```
298
+
299
+ ## Tests
300
+
301
+ - `tests/unit` — time, bounds, analytics, config
302
+ - `tests/contract` — AESO APIM fixtures via `respx`
303
+ - `tests/mcp` — tool/resource discovery and structured outputs
304
+ - `tests/integration` — opt-in live AESO calls
305
+
306
+ ## Security
307
+
308
+ See [SECURITY.md](SECURITY.md). Highlights: no arbitrary URL/shell/SQL tools, host allow-list, secret hygiene, bounded queries, stderr logging for stdio.
309
+
310
+ ## Roadmap
311
+
312
+ - Optional DuckDB/Parquet historical analytics store
313
+ - Broader forecast vs actual tools
314
+
315
+ ## Contributing
316
+
317
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
318
+
319
+ ## License
320
+
321
+ MIT — see [LICENSE](LICENSE).
322
+
323
+ ## Disclaimer
324
+
325
+ This project is an independent open-source interface to publicly documented AESO APIs. It is **not** an official AESO product and is **not affiliated with or endorsed by AESO**. Market data may be preliminary or incomplete; verify critical decisions against official AESO publications.
@@ -0,0 +1,289 @@
1
+ # aeso-mcp
2
+
3
+ <!-- mcp-name: io.github.bchoi-qwe/aeso-mcp -->
4
+
5
+ **Agent-native, strongly typed access and analytics for Alberta's electricity market using official AESO data.**
6
+
7
+ > Independent open-source project. **Not affiliated with or endorsed by the Alberta Electric System Operator (AESO).**
8
+
9
+ ## What it is
10
+
11
+ `aeso-mcp` is a Model Context Protocol (MCP) server that exposes Alberta electricity-market observations and deterministic analytics to AI clients. It is designed for energy analysts, researchers, developers, journalists, market participants, and AI agents that need reliable, structured AESO data—not a thin REST decorator layer.
12
+
13
+ ## Features
14
+
15
+ - Typed MCP tools with Pydantic inputs/outputs and structured results
16
+ - Current market snapshot combining price, load, generation, interchange, and reserves
17
+ - Paginated historical Pool Price, System Marginal Price, load, and generation retrieval
18
+ - Authenticated APIM reports for merit order, commitments, capability/outages, interties,
19
+ metered volumes, and operating-reserve offer control
20
+ - Deterministic analytics: compact history summaries, period comparison, event detection,
21
+ condition evidence, forecast accuracy, and transparent supply-tightness indicators
22
+ - One complete server package and startup path: `AESO_API_KEY` is always required; there is no
23
+ reduced credential-free server mode
24
+ - Query bounds, cache provenance, completeness metadata, upstream `Retry-After` handling, and
25
+ secret-safe machine-readable errors
26
+ - Hardened HTTP transport with Host/Origin validation, optional bearer authentication, rate and
27
+ concurrency limits, request-size bounds, probes, and correlation IDs
28
+ - Reusable MCP prompts plus glossary, capability, dataset, and methodology resources
29
+
30
+ ## Implemented datasets
31
+
32
+ | Dataset | Tool | Notes |
33
+ | --- | --- | --- |
34
+ | Market snapshot | `get_market_snapshot` | Current cohesive view |
35
+ | Pool Price | `get_pool_prices` | Hourly CAD/MWh |
36
+ | System Marginal Price | `get_system_marginal_prices` | Minute-level CAD/MWh |
37
+ | Alberta Internal Load | `get_load` | MW; optional forecast |
38
+ | Generation / fuel mix | `get_generation` | Current all fuels; historical wind/solar |
39
+ | Interchange | `get_interchange` | Current path flows MW |
40
+ | Operating reserves | `get_reserves` | Current MW indicators |
41
+ | Generator outages | `get_outages` | Hourly outage capacity by fuel/technology |
42
+ | AIES capacity/outages | `get_generation_capacity` | Hourly MC, AC, operating, and mothball outage MW |
43
+ | Load outage forecast | `get_load_outage_forecast` | Hourly forecast MW |
44
+ | Energy Merit Order | `get_energy_merit_order` | Historical blocks; 60-day publication delay |
45
+ | Unit commitments | `get_unit_commitments` | Generating-unit commitment directives |
46
+ | Intertie capability | `get_intertie_capability` | Import/export ATC, TTC, margins, gross offers |
47
+ | Intertie capability outages | `get_intertie_outages` | Outages affecting interties/flowgates |
48
+ | Metered volumes | `get_metered_volumes` | Hourly MWh by asset; optional ID filters |
49
+ | OR offer control | `get_operating_reserve_offer_control` | Historical reserve offer blocks; 60-day delay |
50
+ | Approved Tx outages | `get_approved_transmission_outages` | AESO-approved planned transmission outages |
51
+ | Long-range Tx outages | `get_long_range_transmission_outages` | Tentative ~24-month significant outages |
52
+ | MCSINR | `get_monthly_cumulative_net_revenue` | Cumulative net revenue vs offer-cap trigger |
53
+ | Secondary offer limit | `get_secondary_offer_price_limit` | Whether secondary offer cap is in effect |
54
+ | Assets | `get_assets` | Registry with filters |
55
+
56
+ Analytics: `summarize_market_history`, `assess_supply_tightness`, `compare_market_periods`,
57
+ `find_price_events`, `explain_market_conditions`, and `compare_forecast_to_actual`.
58
+
59
+ ## Architecture
60
+
61
+ ```text
62
+ MCP clients
63
+ |
64
+ v
65
+ FastMCP adapter (aeso_mcp/mcp)
66
+ |
67
+ v
68
+ Domain services (market, grid, assets, operations, analytics, transmission, market power)
69
+ |
70
+ +---------------------+------------------------+
71
+ | | |
72
+ v v v
73
+ GridStatus provider Direct AESO APIM Public-reports client
74
+ | | |
75
+ +----------+----------+ |
76
+ | |
77
+ v v
78
+ AESO APIM gateway ets.aeso.ca
79
+ ```
80
+
81
+ Domain code does not depend on FastMCP. Framework changes should stay in `aeso_mcp/mcp/`.
82
+ Both upstream clients are implementation details of this single server: the APIM key is sent only
83
+ to `apimgw.aeso.ca` and is never sent to the allow-listed `ets.aeso.ca` report host.
84
+
85
+ ## Requirements
86
+
87
+ - Python 3.13+
88
+ - AESO APIM API key from [developer-apim.aeso.ca](https://developer-apim.aeso.ca/)
89
+ - [`uv`](https://docs.astral.sh/uv/) recommended
90
+
91
+ ## Installation
92
+
93
+ See [LIMITATIONS.md](LIMITATIONS.md) for an honest gap inventory.
94
+
95
+ ### From PyPI (recommended)
96
+
97
+ ```bash
98
+ export AESO_API_KEY=your-key
99
+ uvx aeso-mcp
100
+ ```
101
+
102
+ ### From GitHub
103
+
104
+ To run the current repository version directly:
105
+
106
+ ```bash
107
+ export AESO_API_KEY=your-key
108
+ uvx --from git+https://github.com/bchoi-qwe/aeso-mcp.git aeso-mcp
109
+ ```
110
+
111
+ ### Development
112
+
113
+ ```bash
114
+ git clone https://github.com/bchoi-qwe/aeso-mcp.git
115
+ cd aeso-mcp
116
+ uv sync --group dev
117
+ cp .env.example .env # set AESO_API_KEY
118
+ uv run aeso-mcp
119
+ ```
120
+
121
+ ### Docker
122
+
123
+ ```bash
124
+ docker build -t aeso-mcp .
125
+ docker run --rm -e AESO_API_KEY=your-key -p 8000:8000 aeso-mcp
126
+ ```
127
+
128
+ ## Obtaining an AESO API key
129
+
130
+ 1. Register at the [AESO developer portal](https://developer-apim.aeso.ca/)
131
+ 2. Subscribe to the AESO public API product
132
+ 3. Copy the primary/secondary subscription key
133
+ 4. Set `AESO_API_KEY` in your environment (never commit it)
134
+
135
+ Missing credentials produce an actionable startup error. The key is never returned through MCP tools or logged.
136
+
137
+ ## Example MCP client configuration
138
+
139
+ ### Cursor / Claude Desktop style (stdio)
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "aeso": {
145
+ "command": "uvx",
146
+ "args": ["aeso-mcp"],
147
+ "env": {
148
+ "AESO_API_KEY": "your-key"
149
+ }
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### HTTP transport
156
+
157
+ ```bash
158
+ uv run aeso-mcp --transport http --host 127.0.0.1 --port 8000
159
+ ```
160
+
161
+ HTTP always validates Host and Origin. For a remotely reachable deployment, explicitly set
162
+ `AESO_MCP_HTTP_ALLOWED_HOSTS` and `AESO_MCP_HTTP_ALLOWED_ORIGINS`; set
163
+ `AESO_MCP_HTTP_BEARER_TOKEN` to require bearer authentication. `/healthz` and `/readyz` contain no
164
+ market data or secrets. See [.env.example](.env.example) for all bounded runtime settings.
165
+
166
+ ## Example prompts
167
+
168
+ - What is Alberta's current grid situation?
169
+ - What is the current pool price?
170
+ - Show Alberta pool prices over the last 24 hours.
171
+ - Compare today's pool prices with yesterday's.
172
+ - Which hours had the highest prices this week?
173
+ - How much wind and solar are producing right now?
174
+ - What happened during the largest price spike this week?
175
+ - Explain the evidence associated with today's price increase.
176
+
177
+ ## Tools
178
+
179
+ | Tool | Purpose |
180
+ | --- | --- |
181
+ | `get_market_snapshot` | Current market overview |
182
+ | `get_pool_prices` | Hourly Pool Price history |
183
+ | `get_system_marginal_prices` | Minute-level SMP history |
184
+ | `get_load` | Alberta Internal Load |
185
+ | `get_generation` | Fuel mix / renewable history |
186
+ | `get_interchange` | Intertie flows |
187
+ | `get_reserves` | Operating reserve indicators |
188
+ | `get_outages` | Hourly generator outage capacity by fuel |
189
+ | `get_generation_capacity` | AIES capability and outage grouping by fuel |
190
+ | `get_load_outage_forecast` | Hourly load-outage forecast |
191
+ | `get_energy_merit_order` | Historical energy merit-order blocks |
192
+ | `get_unit_commitments` | Generating-unit commitment directives |
193
+ | `get_intertie_capability` | Intertie/flowgate ATC, TTC, and margins |
194
+ | `get_intertie_outages` | Outages affecting intertie capability |
195
+ | `get_metered_volumes` | Metered energy by asset |
196
+ | `get_operating_reserve_offer_control` | Historical reserve offer-control blocks |
197
+ | `get_approved_transmission_outages` | Approved planned transmission outages |
198
+ | `get_long_range_transmission_outages` | Tentative long-range transmission outages |
199
+ | `get_assets` | Asset registry |
200
+ | `get_monthly_cumulative_net_revenue` | Current MCSINR publication |
201
+ | `get_secondary_offer_price_limit` | Current secondary offer-cap status |
202
+ | `compare_market_periods` | Aggregate period comparison |
203
+ | `find_price_events` | High-price event detection |
204
+ | `explain_market_conditions` | Structured evidence (not causal prose) |
205
+ | `compare_forecast_to_actual` | AIL forecast vs actual accuracy |
206
+ | `summarize_market_history` | Compact hourly/daily/weekly/monthly price and load summaries |
207
+ | `assess_supply_tightness` | Transparent reserve-adjusted supply-margin screening |
208
+
209
+ All tools are read-only, non-destructive, and network-dependent.
210
+
211
+ ## Resources
212
+
213
+ | URI | Content |
214
+ | --- | --- |
215
+ | `aeso://glossary` | Market terminology |
216
+ | `aeso://datasets` | Dataset catalog |
217
+ | `aeso://methodology/pool-price` | Pool Price interpretation |
218
+ | `aeso://methodology/system-marginal-price` | SMP interpretation |
219
+ | `aeso://capabilities` | Complete tool, prompt, and resource surface |
220
+ | `aeso://methodology/{dataset}` | Dataset-specific interpretation and caveats |
221
+
222
+ Prompts: `daily_market_brief`, `investigate_price_event`, and `compare_market_days`.
223
+
224
+ ## Data semantics
225
+
226
+ - **Timezone**: `America/Edmonton` (AESO market time). DST days may have 23 or 25 local hours.
227
+ - **Intervals**: Explicit `interval_start` / `interval_end` (half-open ranges in requests).
228
+ - **Units**: Pool Price / SMP → CAD/MWh; load / generation / interchange / reserves → MW.
229
+ - **Status**: Metadata includes `actual` / `forecast` / etc. Forecasts are never implied to be settled actuals.
230
+ - **Finality**: Operational feeds may be preliminary; do not assume final settlement.
231
+ - **Completeness**: Metadata reports available/missing series and expected/missing observations
232
+ where the source cadence is known. Optional enrichment failures are surfaced as partial or
233
+ degraded results with warnings.
234
+ - **Pagination**: Raw price, SMP, load, generation, and operational reports return `page` metadata
235
+ with `next_offset`. Use `summarize_market_history` before retrieving long raw series.
236
+ - **Cache timing**: `retrieved_at` identifies the upstream fetch; `served_at`, `cache_hit`, and
237
+ `cache_age` identify when and how the response was served.
238
+
239
+ ## Development
240
+
241
+ ```bash
242
+ uv sync --group dev
243
+ uv run ruff check src tests
244
+ uv run pyright src
245
+ uv run pytest tests/unit tests/contract tests/mcp --cov=aeso_mcp
246
+ uv build
247
+ ```
248
+
249
+ Optional live tests:
250
+
251
+ ```bash
252
+ AESO_API_KEY=... uv run pytest tests/integration -m integration
253
+ ```
254
+
255
+ MCP Inspector:
256
+
257
+ ```bash
258
+ # Prefer the console entrypoint; or point Inspector at:
259
+ # uv run aeso-mcp
260
+ npx @modelcontextprotocol/inspector uv run aeso-mcp
261
+ ```
262
+
263
+ ## Tests
264
+
265
+ - `tests/unit` — time, bounds, analytics, config
266
+ - `tests/contract` — AESO APIM fixtures via `respx`
267
+ - `tests/mcp` — tool/resource discovery and structured outputs
268
+ - `tests/integration` — opt-in live AESO calls
269
+
270
+ ## Security
271
+
272
+ See [SECURITY.md](SECURITY.md). Highlights: no arbitrary URL/shell/SQL tools, host allow-list, secret hygiene, bounded queries, stderr logging for stdio.
273
+
274
+ ## Roadmap
275
+
276
+ - Optional DuckDB/Parquet historical analytics store
277
+ - Broader forecast vs actual tools
278
+
279
+ ## Contributing
280
+
281
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
282
+
283
+ ## License
284
+
285
+ MIT — see [LICENSE](LICENSE).
286
+
287
+ ## Disclaimer
288
+
289
+ This project is an independent open-source interface to publicly documented AESO APIs. It is **not** an official AESO product and is **not affiliated with or endorsed by AESO**. Market data may be preliminary or incomplete; verify critical decisions against official AESO publications.