Skip to main content
Glama
MapleStats MCP

MapleStats MCP

README.md
<p align="center">
  <img src="https://raw.githubusercontent.com/dsanchezp18/maplestats-mcp/main/site/assets/logo.svg" width="160" height="160" alt="MapleStats MCP logo: a white pixel maple leaf over the name maplestats-mcp on a red square">
</p>
<p align="center">
  <h1 align="center">MapleStats MCP</h1>
  <p align="center">
    <strong>One MCP server for Canadian open data.</strong>
  </p>
  <p align="center">
    <a href="https://pypi.org/project/maplestats-mcp/"><img src="https://img.shields.io/pypi/v/maplestats-mcp" alt="PyPI version"></a>
    <a href="https://pypi.org/project/maplestats-mcp/"><img src="https://img.shields.io/pypi/pyversions/maplestats-mcp" alt="Supported Python versions"></a>
    <a href="https://github.com/dsanchezp18/maplestats-mcp/blob/main/LICENSE"><img src="https://img.shields.io/github/license/dsanchezp18/maplestats-mcp" alt="License: MIT"></a>
    <a href="https://pypistats.org/packages/maplestats-mcp"><img src="https://img.shields.io/pypi/dm/maplestats-mcp" alt="PyPI downloads per month"></a>
    <a href="https://github.com/dsanchezp18/maplestats-mcp/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dsanchezp18/maplestats-mcp/ci.yml?branch=main&label=CI" alt="CI status"></a>
    <a href="https://maplestats.danielstats.io/"><img src="https://img.shields.io/github/actions/workflow/status/dsanchezp18/maplestats-mcp/pages.yml?branch=main&label=website" alt="Website build status"></a>
    <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
    <a href="https://m8ven.ai/mcp/dsanchezp18/maplestats-mcp"><img src="https://m8ven.ai/badge/mcp/dsanchezp18/maplestats-mcp" alt="M8ven Score"></a>
    <a href="https://mcplookup.com/server/io.github.dsanchezp18/maplestats-mcp"><img src="https://mcplookup.com/badge/io.github.dsanchezp18/maplestats-mcp" alt="MCPLookup Trust Index"></a>
    <a href="https://glama.ai/mcp/servers/dsanchezp18/maplestats-mcp"><img src="https://glama.ai/mcp/servers/dsanchezp18/maplestats-mcp/badges/score.svg" alt="MapleStats MCP server quality and maintenance score on Glama"></a>
    <a href="https://mcprush.com/to/maplestats-mcp?ref=badge"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fmcprush.com%2Fbadge%2Fmaplestats-mcp%2Fshields.json&amp;query=%24.message&amp;label=mcprush&amp;color=BEF264&amp;labelColor=1A1A1A" alt="MapleStats MCP grade on Mcprush"></a>
    <a href="https://lobehub.com/mcp/dsanchezp18-maplestats-mcp"><img src="https://lobehub.com/badge/mcp/dsanchezp18-maplestats-mcp" alt="MapleStats MCP on LobeHub"></a>
    <a href="https://www.piwheels.org/project/maplestats-mcp/"><img src="https://img.shields.io/badge/piwheels-Raspberry%20Pi-C51A4A" alt="Wheels for Raspberry Pi on piwheels"></a>
    <a href="https://registry.modelcontextprotocol.io/?q=io.github.dsanchezp18/maplestats-mcp"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fregistry.modelcontextprotocol.io%2Fv0%2Fservers%2Fio.github.dsanchezp18%252Fmaplestats-mcp%2Fversions%2Flatest&query=%24.server.version&label=MCP%20Registry&prefix=v&color=blue" alt="MCP Registry version"></a>
  </p>
</p>

<!-- mcp-name: io.github.dsanchezp18/maplestats-mcp -->

MapleStats MCP gives AI agents (Claude, Cursor and any MCP client) typed
access to Canadian open data through one server: Statistics Canada
(tables, Census, public use microdata), the Bank of Canada, CMHC, federal
agencies, and federal, provincial, territorial and municipal open-data
portals, in English and French. Every result carries its source.

*MapleStats MCP donne aux agents IA un accès structuré aux données
ouvertes canadiennes par un seul serveur, en français et en anglais.*

## Why MapleStats

Built by a statistician, for anyone who works with the numbers: economists,
demographers, policy analysts, researchers, journalists, students, anyone who
needs a Canadian figure they can trust and cite. MapleStats is designed around
the mistakes people actually make with official data, and we test it against
other tools on public benchmarks: [see how it does](https://maplestats.danielstats.io/benchmarks.html).

- **It knows the traps.** Constant vs current dollars, family vs individual
  income, annual average vs December over December, survey rent vs listing
  rent: it warns in plain language, and each fitness-for-use caution rests on
  the publisher's own documentation.
- **Every number names its source:** every StatCan series carries a citation
  (table, vector, release date, with the period, unit, geography and a link to
  the official table); every other result names its source and licence.
- **It says when data do not exist.** A series that is not published, a period
  not released yet or a date with no data gets a plain no, and the nearest
  figure that does exist.
- **The answer can be checked.** Before replying, the assistant can send the
  StatCan and Bank of Canada figures it cites to `check_answer`, which re-reads
  them from the source.
- **Your work stays reproducible.** It writes the R, Python, Stata or Julia
  script, or the Excel query, that pulls the same data from the source.
- **Quality flags and revisions, shown:** "use with caution", "too unreliable
  to be published", preliminary and revised are spelled out on every StatCan
  value.
- **It only reads, in English and French.** The server only reads from public
  sources. The scripts it writes run only if you run them (the full-table ones
  then save the download under `data/raw/`), and saving a workbook to disk is
  opt-in (`MAPLE_EXPORT_DIR`, on a local server).

Project vision: [`docs/PROJECT_GUIDE.md`](https://github.com/dsanchezp18/maplestats-mcp/blob/main/docs/PROJECT_GUIDE.md).
Source coverage: [`docs/ROADMAP.md`](https://github.com/dsanchezp18/maplestats-mcp/blob/main/docs/ROADMAP.md).
Website: <https://maplestats.danielstats.io/>.

## The easiest way: ask your agent

1. Copy this prompt.
2. Paste it into Claude Code, Codex, Cursor or any agent that can run
   commands on your computer.
3. Restart the agent when it says so, then ask for data.

> Connect the MapleStats MCP server to this agent. Follow the setup steps in https://github.com/dsanchezp18/maplestats-mcp

### Instructions for the agent doing the setup

The default is the hosted server: nothing to install, no account, no key.

1. Register this remote (HTTP) MCP server under the name `maplestats`:
   - **Claude Code:** `claude mcp add --transport http --scope user maplestats https://mcp.maplestats.danielstats.io/mcp`
   - **Codex CLI:** `codex mcp add maplestats --url https://mcp.maplestats.danielstats.io/mcp`
   - **Cursor, Claude Desktop and other `mcpServers` clients:** add the entry
     below to the client's config file (Cursor: `~/.cursor/mcp.json`; Claude
     Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`
     on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows),
     keeping any servers already there.
   - **claude.ai and the Claude phone app:** on the web, open Settings →
     Connectors → Add custom connector, name it `MapleStats`, and enter
     `https://mcp.maplestats.danielstats.io/mcp`. Then switch it on from the tools
     menu in a chat, on the web or in the phone app. Claude asks for approval
     before each call by default; every tool is read-only, so set them to
     always allow in the connector's tool permissions.
   - **ChatGPT (web):** turn on Settings → Security and login → Developer mode,
     then create a developer-mode app for a remote MCP server with the same
     address and No Authentication. Every tool is read-only, so ChatGPT
     should not ask to confirm calls. OpenAI documents this feature for the web.
   - **A whole organization:** on Claude Team or Enterprise, an Owner adds it once
     under Organization settings → Connectors → Add custom connector, and each
     member then connects it. On ChatGPT Business, Enterprise or Edu, an admin
     turns on Developer mode (Workspace Settings → Permissions & Roles →
     Connected Data), creates the app, and publishes it (Workspace settings →
     Apps → Drafts → Publish). The hosted server is shared and limits each
     client to 60 requests a minute, so a large organization should run its own
     copy.
   - **VS Code:** the same entry under a `servers` key, with `"type": "http"`,
     in `.vscode/mcp.json`.

   ```json
   {
     "mcpServers": {
       "maplestats": {
         "url": "https://mcp.maplestats.danielstats.io/mcp"
       }
     }
   }
   ```
2. Tell the user to restart or reload the client, then check that the
   `search_tools` tool is listed.
3. Only if the client cannot use remote servers, or the user wants the
   microdata tabulation tool or everything kept on their own machine, run it
   locally instead: `claude mcp add --scope user maplestats -- uvx maplestats-mcp`
   (needs [uv](https://docs.astral.sh/uv/)), or the equivalent `uvx
   maplestats-mcp` entry for other clients (see [Install](#install)).

Each client gets 60 requests a minute, and the microdata tabulation tool is
switched off there. Your agent's tool calls (a search phrase, a table number)
reach the server and its host, Microsoft Azure (Canada Central). The code keeps
no log of them, but its in-memory cache can hold a search phrase or filter in a
key or a response until the entry expires or the server restarts. Check
Microsoft's [terms](https://azure.microsoft.com/support/legal/) and [privacy statement](https://privacy.microsoft.com/privacystatement); for full privacy, run it locally. What it logs:
[FAQ](https://maplestats.danielstats.io/faq.html#hosted). Other
clients: the [Connect](https://maplestats.danielstats.io/connect.html)
page.

## Install locally (optional)

Prefer to run it on your machine? It works over stdio with no account or key.

```bash
uvx maplestats-mcp                 # run without installing
uv tool install maplestats-mcp     # or install the command once (pip works too)
uv tool upgrade maplestats-mcp     # update
```

For the development version: `uv tool install git+https://github.com/dsanchezp18/maplestats-mcp.git`.
If you installed the command, use `"command": "maplestats-mcp"` with no
`args` in the JSON above. On Windows, make sure the directory where `uv`
installs tools is on `PATH`, and restart the client afterwards.

## Using it

Tools are found through search rather than listed flat: call
`search_tools` with a plain-language query (English or French), then
`call_tool` with the name it returns.

```json
{"name": "search_tools", "arguments": {"query": "consumer price index"}}
{"name": "call_tool", "arguments": {"name": "wds_search_cubes", "arguments": {"query": "consumer price index"}}}
```

`plan_query` turns a question into an ordered, multi-source plan, and
`reproduce_code` writes the R, Python, Stata or Julia script (or an Excel
Power Query) that fetches the same data straight from the source;
`reproduce_workbook` returns the rows as a formatted Excel workbook.
Before replying with figures, the assistant can call `check_answer` with the
StatCan and Bank of Canada figures it cites: it re-reads them from the source
and returns only the problems (a different value, a missed scale factor, a
newer period, another geography, an unadjusted series), each with a fix.
Every tool accepts `lang: "en"|"fr"`;
some sources only publish English content (for example Yukon's CKAN portal,
the World Bank's indicator definitions and the PMPRB), so there `lang`
changes the labels MapleStats adds, not the source's own text. Every response
has a `provenance` block: source, URL, query time, freshness and limits.
`docs://catalogue` describes every module in both languages. The guided
prompts take `lang` too, and each `docs://<module>/...` resource has a French
twin at `docs://<module>/fr/...` (for example `docs://statcan/fr/pieges`).

To call the sources from Python without MCP, see [`docs/library.md`](https://github.com/dsanchezp18/maplestats-mcp/blob/main/docs/library.md).

## What to watch for

MapleStats retrieves data directly from the original sources and returns
values as published. When a result is trimmed, scaled, cleaned or computed,
it says so in its `provenance` block or its notes; series values come
unscaled, with the scale factor beside them. When a source is down or blocks
the request, the tool returns an error, not a silent gap or a substitute
value.

The risk is in the use. An AI assistant can pick the wrong table, geography
or reference period, and the answers, summaries and analysis it writes are
AI-generated and can contain errors. For anything you would publish,
spot-check a few figures against the source's own site and use
`reproduce_code` to fetch them again outside the assistant. Publishers revise
their figures, so a value retrieved today can differ from an earlier one.

Each dataset keeps its own licence and attribution terms, shown in the
result's `provenance`; credit the publisher, not MapleStats. MapleStats is an
independent project, not affiliated with or endorsed by Statistics Canada,
the Bank of Canada, CMHC or any other data provider. It is provided as is,
depends on upstream services that can change or go offline, and is not
professional, legal or financial advice. Parts of the code and tests were
written with AI assistance; the discovery benchmark's questions were
written by one AI session and reviewed by a second, independent one, not yet
by a person
([`tests/discovery_benchmark/REVIEW.md`](tests/discovery_benchmark/REVIEW.md)).

## What it covers

399 tools in 78 modules. Run `docs://catalogue` for the full, bilingual list.

| Area | Tool prefixes | Covers |
|---|---|---|
| Statistics Canada | `wds_`, `sdmx_`, `sdmx_space_`, `rdaas_`, `cimt_`, `statcan_*` | Tables and series, the extra SDMX spaces (energy information, shared data), classifications, Census Profiles 2001–2021, public use microdata (codebooks, weighted tables), merchandise trade by commodity (CIMT), open databases (LODE), The Daily, delta files, indicators, surveys, census geography |
| Bank of Canada | `boc_` | Valet series, groups, observations |
| CMHC | `cmhc_`, `cmhc_dt_` | Housing Market Information Portal and Excel data tables |
| Federal agencies | `eccc_`, `eccc_coverages_`, `eccc_datamart_`, `ised_*`, `gazette_`, `tc_recalls_`, `recalls_`, `cdc_`, `cfia_`, `fcac_`, `finance_`, `cihi_`, `phac_infobase_`, `hc_*`, `pmra_`, `pmprb_`, `gc_infobase_`, `disclosure_`, `cer_`, `nrcan_*`, `dfo_iwls_`, `cgc_`, `nfd_`, `ircc_*`, `cbsa_`, `cbsa_tariff_`, `pbo_`, `competition_bureau_`, `elections_results_`, `elections_financial_returns_`, `cra_digital_economy_registry_`, `cra_charities_`, `jobbank_`, `telecom_`, `earthquakes_`, `borealis_`, `isc_` | Weather, climate and gridded climate projections, pollutant releases and emissions (NPRI, GHGRP, NAPS), corporations and IP, regulations, recalls, dairy, animal disease, consumer banking, federal and provincial fiscal tables, health, drugs, natural health products, medical devices, adverse reactions, pesticides, spending, federal contracts, grants and travel and hospitality disclosures, energy, mineral production, oceans, grain, forestry, immigration and application processing times, Job Bank wages and outlooks by occupation, registered charities, mobile and Internet plan prices, border wait times, customs tariff rates, budgets, mergers, elections, earthquakes, university-library data tables, First Nation profiles, registered population and drinking water advisories |
| Wildland fire | `cwfis_`, `nrcan_nbac_`, `ab_wildfire_` | NRCan's Canadian Wildland Fire Information System (satellite hotspots, fire danger, weather stations), burned areas, and Alberta Wildfire's live fire status |
| Electricity | `electricity_ontario_`, `electricity_quebec_`, `oeb_` | IESO (Ontario) demand, generation and prices; Hydro-Québec demand, generation and trade (CC BY-NC 4.0: credit Hydro-Québec, non-commercial use only); Ontario Energy Board datasets |
| Parliament and elected officials | `ourcommons_`, `senate_`, `represent_` | MPs and their roles, party standings, Cabinet, Senate votes; elected officials and districts for a postal code via Open North's Represent (unofficial) |
| Provincial agencies | `aer_`, `bcgw_`, `bc_stats_`, `bc_env_`, `bc_lobbyists_`, `drivebc_`, `ab_economic_`, `ab_opendata_`, `isq_`, `nl_stats_`, `yukon_stats_`, `nwt_stats_`, `elections_provincial_`, `on_fir_` | Alberta Energy Regulator, BC Geographic Warehouse, BC Stats Excel tables, BC environmental monitoring (air quality, snow, groundwater, streamflow), BC lobbyists registry, DriveBC road events, Alberta Economic Dashboard, Open Alberta files, Institut de la statistique du Québec, the Newfoundland and Labrador, Yukon and Northwest Territories statistics bureaus, provincial general election results (Quebec, Alberta, British Columbia, Saskatchewan, Manitoba), Ontario's municipal Financial Information Return (every schedule of every municipality, 2000 on) |
| Housing (links only) | `crea_` | Links, release timing and terms for CREA's MLS® Home Price Index; no values, because CREA's terms allow private, non-commercial analysis only |
| International | `worldbank_`, `bis_` | World Bank World Development Indicators for Canada and peer countries (CC BY 4.0); Bank for International Settlements statistics: policy rates, debt service ratios, credit, house prices, banking and payment statistics (free reuse with citation) |
| Open-data portals | `ckan_`, `arcgis_hub_`, `socrata_` + `portal` | Federal, provincial, territorial and municipal catalogues (`*_list_portals` names each one) |
| Other municipal | `opendatasoft_vancouver_`, `nl_opendata_`, `eps_`, `ets_`, `epcor_` | Vancouver, Newfoundland and Labrador, Edmonton police, transit and water quality |
| Transit schedules | `transit_` + `agency` | Static GTFS timetables of the STM (buses), OC Transpo, Calgary Transit, VIA Rail, GO Transit, UP Express, the BC Transit systems and the Quebec networks listed on Données Québec (exo, RTC, STL, STS and others), plus further agencies from Statistics Canada's 2025 Canadian Public Transit Network Database: stops, routes, scheduled departures, frequency by hour |

Other federal series (CRA, OSFI, ISED insolvency) are ordinary
open.canada.ca datasets, reachable with
`ckan_search_datasets(portal="federal", fq="organization:<org>")`.

Not covered: CAPP's Statistics Handbook, Petrinex and Payments Canada were
not built, because their terms or download hosts do not permit automated
access. CanadaBuys, OpenParliament.ca and the Toronto Transit Commission's
own schedule download were removed for the same reason (the TTC schedule is
still served from Statistics Canada's national transit database). The
City of Hamilton's portal and Niagara Open Data (niagaraopendata.ca) are left
out because they answer automated requests with a Cloudflare challenge. The 2006-2016 census data tables and the 2016 Census Profile service were removed because Statistics Canada's www12 host answers automated clients with a bot challenge, 503 errors or timeouts (the 2021 profile and the WDS tables remain). Justice Laws (federal statutes and
regulations) is out of scope, because legislation text is not data this
server carries, and the Saskatchewan Bureau of Statistics' Provincial
Economic Accounts were not built: they are under Crown copyright, for
non-commercial reuse only, and the site's terms bar automated access. The
[roadmap](https://github.com/dsanchezp18/maplestats-mcp/blob/main/docs/ROADMAP.md)
records each decision.

## Development

```bash
uv sync                          # install
uv run ruff check src tests scripts  # lint
uv run ruff format src tests scripts  # format
uv run pyright                   # type check
uv run pytest                    # unit tests (mocked, no network)
```

`.\scripts\verify.ps1` runs that gate plus every `scripts/smoke_test*.py`
against the live APIs. The website is generated from the tool registry:
`uv run python scripts/build_site.py` writes `build/site/`. See
[`AGENTS.md`](https://github.com/dsanchezp18/maplestats-mcp/blob/main/AGENTS.md)
for the contributor guide and how to add a source module.

## Hosting

`MAPLE_TRANSPORT=http MAPLE_HOST=0.0.0.0 MAPLE_PORT=8000 uv run maplestats-mcp`
serves HTTP instead of stdio. Docker Compose does the same in Docker, but
`docker-compose.yml` sets `MAPLE_REQUIRE_AUTH=1`, so the server refuses to
start until `MAPLE_AUTH_TOKEN` is set:
`MAPLE_AUTH_TOKEN=<a long random string> docker compose up --build`. Clients
then send that token as a bearer token. Docker is optional and unnecessary on
a personal computer. If the server is exposed beyond your machine, set
`MAPLE_AUTH_TOKEN` and keep `MAPLE_REQUIRE_AUTH=1`. `GET /health` reports uptime and version. `GET /stats`
is public, needs no token and reports calls per tool name and day, each tool's
p50 and p95 duration, and each upstream host's error counts and circuit-breaker
state (see `MAPLE_USAGE_STATS` and `MAPLE_BREAKER` below). After repeated
failures (timeouts, connection errors, 5xx answers, Cloudflare challenges) a
host is paused for a cooldown: its tools fail at once with an "unavailable"
error that says when to retry, instead of waiting for another timeout. The
`docs://status` resource lists the paused hosts.

| Env var | Default | Purpose |
|---|---|---|
| `MAPLE_TRANSPORT` | `stdio` | `stdio` for local clients, `http` for hosting |
| `MAPLE_HOST` / `MAPLE_PORT` | `127.0.0.1` / `8000` | HTTP bind address |
| `MAPLE_AUTH_TOKEN`, `MAPLE_REQUIRE_AUTH` | unset, `0` | Bearer token required on `/mcp`; refuse to start without one if `1`. `docker-compose.yml` defaults `MAPLE_REQUIRE_AUTH` to `1` |
| `MAPLE_RATE_LIMIT_REQUESTS` / `MAPLE_RATE_LIMIT_WINDOW_SECONDS` | `120` / `60` | Per-client rate limit |
| `MAPLE_MAX_CONCURRENT_REQUESTS` | `8` | In-flight request cap; the excess waits 5 s, then gets 503 |
| `MAPLE_SSL_CERTFILE` / `MAPLE_SSL_KEYFILE` | unset | TLS in-process |
| `MAPLE_TRUST_PROXY_HEADERS` | `0` | Rate-limit on `X-Forwarded-For`; only behind a proxy that sets it |
| `MAPLE_TRUSTED_PROXY_HOPS` | `1` | Proxies that append to `X-Forwarded-For`; the client address is read that many entries from the right |
| `MAPLE_ALLOWED_ORIGINS` | project website, localhost | Browser origins allowed on `/mcp` (comma-separated, `*.` and `:*` wildcards); others get 403, clients without an Origin are allowed |
| `MAPLE_CACHE_MAX_ENTRIES` | `2000` | Response cache size per TTL bucket |
| `MAPLE_CACHE_MAX_MB` | `128` | Estimated memory cap for the response cache, all buckets together |
| `MAPLE_PARSE_WORKERS` / `MAPLE_PARSE_TIMEOUT_SECONDS` | `4` / `60` | Threads for parsing Excel and CSV files, and the time one parse may take |
| `MAPLE_TOOL_TIMEOUT_SECONDS` | `120` | Longest a tool call may run |
| `MAPLE_BREAKER` | `1` | Pause requests to an upstream host after repeated failures; `0` sends every request |
| `MAPLE_BREAKER_FAILURES` / `MAPLE_BREAKER_WINDOW_SECONDS` / `MAPLE_BREAKER_COOLDOWN_SECONDS` | `5` / `300` / `60` | Failures in a row, within the window, that pause a host, and how long before one probe request is sent |
| `MAPLE_PUMF_TABULATE` | `1` | `0` hides `statcan_pumf_tabulate` (it downloads whole PUMF ZIPs); search, listings and codebooks stay |
| `MAPLE_QUERY_SERIES` | `1` locally, `0` over HTTP | `0` hides `wds_query_series` (server-side SQL): off by default when serving HTTP, since one query could use more memory than a small instance has |
| `MAPLE_PUMF_CACHE_DIR`, `MAPLE_PUMF_CACHE_MAX_GB` | system temp, `5` | Downloaded microdata cache; use a persistent volume when hosted |
| `MAPLE_CLIENT_IP_HEADER` | unset | Header the outermost proxy sets to the caller's own address (Cloudflare's `CF-Connecting-IP`), used for the per-client rate limit when `MAPLE_TRUST_PROXY_HEADERS` is on; set it only when every request passes through that proxy |
| `MAPLE_DUCKDB_MEMORY_LIMIT` | unset | Memory limit for DuckDB queries over downloaded files (patents, open databases, microdata), such as `256MB`; beyond it DuckDB spills to the temporary folder. Set it on a small hosted instance |
| `MAPLE_DUCKDB_TEMP_DIR`, `MAPLE_DUCKDB_TEMP_MAX_GB` | system temp (`maplestats-duckdb`), `1` | Where DuckDB spills past its memory limit (each query in its own subfolder, removed when it ends), and the most one query may spill |
| `MAPLE_QUERY_MEMORY_MB`, `MAPLE_QUERY_MAX_CONCURRENT` | `256`, `2` | `wds_query_series` runs each SQL query in its own process: the memory it may use beyond its start (enforced on Linux), and how many run at once |
| `MAPLE_LODE_CACHE_DIR`, `MAPLE_LODE_CACHE_MAX_GB` | system temp, `3` | Where StatCan open-database (LODE) archives are unpacked, and the size cap (least recently used files go first); use a persistent volume when hosted |
| `MAPLE_LODE_MAX_DOWNLOAD_MB` | `300` | Largest open-database archive one call may download; `0` turns downloads off |
| `MAPLE_IP_HORIZONS_CACHE_DIR`, `MAPLE_IP_HORIZONS_CACHE_MAX_GB` | system temp, `3` | CIPO patent table cache |
| `MAPLE_CBSA_TARIFF_CACHE_DIR` | system temp | Customs Tariff editions as Parquet (under 1 MB each, newest 12 kept) |
| `MAPLE_ON_FIR_CACHE_DIR`, `MAPLE_ON_FIR_CACHE_MAX_MB` | system temp, `500` | Ontario FIR years kept as Parquet (about 3 MB each); use a persistent volume when hosted |
| `MAPLE_EXPORT_DIR` | unset (no files saved) | Folder where `reproduce_workbook` saves Excel workbooks on a local server. Unset, the workbook comes back as base64 and every tool is read-only |
| `MAPLE_SEARCH`, `MAPLE_SEMANTIC_DIR` | `bm25`, unset | `hybrid` fuses BM25 with a local multilingual-e5-small index (needs the `[semantic]` extra and `python -m maplestats_mcp.shared.semantic build`); off by default, see `tests/discovery_benchmark/phase2.md` |
| `MAPLE_RESPONSE_MAX_BYTES` | `200000` | Byte budget for one tool result (minimum 10,000); a larger result has its biggest lists trimmed, and `provenance.limits` says what was kept and how to get the rest |
| `MAPLE_GOATCOUNTER_URL`, `MAPLE_GOATCOUNTER_TOKEN` | unset | With both set (an https GoatCounter site and an API token allowed to record pageviews), each counted tool call is also sent there as the event `mcp/<instance>/<tool>` (`/error` appended on failure), in batches each minute, so counts outlive restarts and several hosts add up; no arguments or addresses, `no_sessions` on |
| `MAPLE_INSTANCE_NAME` | `server` | The `<instance>` label in those events (`azure`, `render`) |
| `MAPLE_USAGE_STATS` | `1` | Counts calls and durations per tool name and UTC day, and upstream errors per host (never arguments or client addresses), in memory, and serves them at the public `/stats` route; `0` turns both off |
| `MAPLE_DELTA_MAX_SCAN_MB` / `MAPLE_DELTA_MAX_SCAN_SECONDS` | `400` / `75` | How much of a Delta File one `statcan_delta_read_table` call streams (seconds kept 30 under the tool timeout) |
| `MAPLE_DELTA_INDEX_DIR` | system temp | Saved Delta File resume points (a few MB per day); `off` keeps them in memory only |

## License

MIT. Third-party notices are in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).

## Security and conduct

Report a vulnerability privately through GitHub's
[private vulnerability reporting](https://github.com/dsanchezp18/maplestats-mcp/security/advisories/new);
see [`SECURITY.md`](SECURITY.md). Changes are recorded in
[`CHANGELOG.md`](CHANGELOG.md), and community expectations in
[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).

## Acknowledgments

MapleStats owes its architecture to
[EcuDataMCP](https://github.com/DweskZ/EcuDataMCP), the MCP server for
Ecuador's open data that I help maintain with
[DweskZ](https://github.com/DweskZ) and its other contributors. Its
approach to Canadian data owes much to the R developers who got there
first: Jens von Bergmann ([mountainMath](https://github.com/mountainMath))
and his co-authors, including Dmitry Shkolnik
([dshkol](https://github.com/dshkol)), Thierry Warin ([statcanR](https://github.com/warint/statcanR)), Valentin
Lucet ([rgovcan](https://github.com/VLucet/rgovcan)), and others. Thanks
to them, and to everyone who publishes Canadian data in the open.

For other ways to get Canadian data, see the
[alternatives](https://maplestats.danielstats.io/about.html#alternatives).

TDQS

A4.5/5.0

Scored across 3 tools

Disambiguation5/5

The three tools occupy clearly separate stages: plan_query plans a multi-source research path offline, search_tools finds a single tool in the catalogue, and call_tool executes a named tool. Each description explicitly says when to prefer the other two (e.g. 'Use search_tools instead to find a single tool' / 'Use plan_query instead when a question may span several sources'), leaving almost no room for misselection.

Naming Consistency5/5

All three names follow the same snake_case verb_noun pattern (plan_query, search_tools, call_tool) with no stylistic deviation. The verb choice is also semantically parallel, reinforcing the discover-then-execute pipeline.

Tool Count4/5

Three tools is thin in absolute terms, but the design is a deliberate gateway: the real data tools live behind call_tool and are surfaced via search_tools, so each of the three earns its place. The only reservation is that a third of the surface is planning rather than data access.

Completeness4/5

The lifecycle of discover -> plan -> execute -> provenance (source, URL, freshness) is covered, with sensible error semantics documented for call_tool. Minor gaps remain: no way to browse or enumerate the full catalogue beyond a five-result search, and no batch/multi-tool execution despite plan_query returning ordered step lists.

Maintenance

ActivityActive
ResponsivenessSyncing