Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
cyanheads avatar

Openaq Mcp Server

  • 2 repo stars
  • Updated July 27, 2026
  • cyanheads/openaq-mcp-server

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.

About

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3. Exposes 7 MCP tools including | Tool | Description |, |. Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3. <p><b>Find air-quality monitoring stations, read latest sensor values, and pull historical pollutant series via MCP. STDIO &amp; Streamable HTTP.</b> <div>7 Tools (2 opt-in) • 2 Resources</div> Install via Claude Desktop, Cursor, or any MCP-compatible client using the server manifest from the upstream repository.

  • Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.
  • 7 registered MCP tools for structured agent access.
  • Category: AI & LLM Tools with build/integrations journey placement.
  • Compatible with Claude Desktop, Cursor, and MCP clients.
  • Upstream manifest: openaq-mcp-server.

Openaq Mcp Server by the numbers

  • Exposes 7 verified tools (MCP introspection)
  • Data as of Jul 27, 2026 (Skillselion catalog sync)
terminal
claude mcp add --env OPENAQ_API_KEY=YOUR_OPENAQ_API_KEY --env OPENAQ_API_BASE_URL=YOUR_OPENAQ_API_BASE_URL --env CANVAS_PROVIDER_TYPE=YOUR_CANVAS_PROVIDER_TYPE --env MCP_LOG_LEVEL=YOUR_MCP_LOG_LEVEL openaq-mcp-server -- npx -y @cyanheads/openaq-mcp-server run

Add your badge

Show developers this MCP server is listed on Skillselion. Paste this into your README.

Listed on Skillselion
repo stars2
Package@cyanheads/openaq-mcp-server
TransportSTDIO, HTTP
AuthRequired
Tools7
Last updatedJuly 27, 2026
Repositorycyanheads/openaq-mcp-server

How do I connect Openaq Mcp Server to my MCP client?

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.

Who is it for?

Teams wiring Openaq Mcp Server into Claude, Cursor, or custom agents for ai & llm tools.

Skip if: Skip when you need a non-MCP SDK or hosted API without stdio/SSE transport.

What you get

Working Openaq Mcp Server MCP server with verified tool registration and client config.

By the numbers

  • [object Object]
At a glance

Openaq Mcp Server capabilities & compatibility

Capabilities
openaq mcp server mcp tool registration · openaq mcp server client configuration · openaq mcp server agent workflow integration
Use cases
api development
Runs
Runs locally
From the docs

What Openaq Mcp Server says it does

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.
README.md

Tools 7

Public tool metadata - what this server can do for an agent.

openaq_find_locations6 params

Find air-quality monitoring stations (measured by physical sensors, not modeled) near a point, within a bounding box, or by country. Returns each station's id, name, coordinates, distance from the query point (when searching by coordinates), country, provider, the parameters its sensors measure, and the timestamp of its most recent data (datetimeLast). Required first step: openaq_get_readings and openaq_get_measurements key on the location id this returns. Coverage is uneven and real — a station only reports the parameters it measures, and the absence of a nearby station means no monitoring there, not clean air. For dense modeled coverage anywhere on Earth, use open-meteo-mcp-server's air-quality tool instead.

  • coordinatesstringCenter point as "latitude,longitude" (e.g. "47.6062,-122.3321"). Pair with radius for a near-me search. Resolve a place name to coordinates with openstreetmap-mcp-server or open-meteo geocode first. Provide either coordinates+radius OR bbox, not both.
  • radiusintegerSearch radius in metres around coordinates (1–25000; the API hard-caps at 25000). Default 12000 (~12km). Only used with coordinates.
  • bboxstringBounding box as "minLon,minLat,maxLon,maxLat" (west,south,east,north). Alternative to coordinates+radius for area sweeps. Results have no distance field (no center point).
  • isostringRestrict to a country by ISO 3166-1 alpha-2 code (e.g. "US", "IN", "DE"). Combine with bbox/coordinates to scope, or use alone for a country-wide list. Discover coverage with openaq_list_countries.
  • parametersIdintegerOnly return stations that measure this parameter id (e.g. 2 = PM2.5 µg/m³). Get ids from openaq_list_parameters — the same pollutant has several ids for different units. Narrows the station set; each returned station still lists all its sensors.
  • limitintegerMax stations to return (1–100). Default 20. Results are ordered by distance when searching by coordinates.
openaq_get_readings3 params

Latest measured value for every sensor at a monitoring station — the current-conditions tool. Returns one record per parameter, each with the value, its unit, the UTC and local timestamp, and the sensor id, joined so every value carries its pollutant and unit (the raw latest feed is keyed only by sensor id). Pass a locationId from openaq_find_locations, or pass coordinates to auto-resolve to the nearest station that measures the requested parametersId. Data recency varies by station reporting cadence — read each value's timestamp to know whether "latest" is minutes or hours old. These are measured observations with coverage gaps, not a modeled grid.

  • locationIdintegerStation id from openaq_find_locations. Provide this OR coordinates. When set, returns the latest value for every sensor at this station.
  • coordinatesstringFallback "latitude,longitude" when you do not have a locationId — resolves to the nearest station (within 25km) that measures parametersId, then reads its latest values. Requires parametersId.
  • parametersIdintegerRequired with coordinates: which parameter id the nearest station must measure (get ids from openaq_list_parameters). With locationId, optionally filters the returned values to this parameter id; omit to get all sensors.
openaq_get_measurements7 params

Historical measurement series for one pollutant at one station over a date range — for trend analysis and "was last week worse than the monthly average?". Pass a locationId and a parametersId; the tool resolves the station's sensor for that parameter internally (v3 series are sensor-scoped, but you think in stations). Choose aggregation: raw (every reported value), hourly, or daily — daily and hourly add a per-bucket statistical summary (min, median, max, mean, sd). Large ranges produce thousands of rows and spill to a DataCanvas: the response returns a preview plus a canvasId and table name you query with openaq_dataframe_query. Values carry their unit; the server never converts between µg/m³, ppm, and ppb.

  • locationIdintegerrequiredStation id from openaq_find_locations.
  • parametersIdintegerrequiredParameter id to pull the series for (e.g. 2 = PM2.5 µg/m³). Get ids from openaq_list_parameters. Must be a parameter the station measures — find_locations lists each station's parameters.
  • datetimeFromstringStart of the range, inclusive. Date "YYYY-MM-DD" or full UTC "YYYY-MM-DDTHH:MM:SSZ". Omit to get the most recent values.
  • datetimeTostringEnd of the range, inclusive. Must be on or after datetimeFrom. Omit for "up to now".
  • aggregationstringTime bucketing. "raw" = every reported value (often hourly at source). "hourly"/"daily" = server-side rollups with a statistical summary per bucket. Use "daily" for multi-month trends to keep the series small; "raw" for fine-grained recent analysis.
  • limitintegerMax rows per page from the API (1–1000). Default 1000. The tool pages internally up to the spill threshold.
  • canvas_idstringDataCanvas id from a prior call to reuse the same canvas (e.g. to compare two stations' series side by side). Omit to start fresh; the response returns a new canvas_id when the series spills.
openaq_list_parameters2 params

Catalog of every measurable pollutant and its canonical unit: id, code, display name, unit, and a one-line description (pm25, pm10, o3, no2, so2, co, bc, and ~38 more). This is the unit-disambiguation reference — the same pollutant exists under several ids with different units (CO is id 4 in µg/m³, id 8 in ppm, id 102 in ppb), so use this to pick the exact parametersId for openaq_find_locations / openaq_get_readings / openaq_get_measurements and to interpret a reading's unit. A small bounded catalog fetched live from OpenAQ.

  • querystringLocal case-insensitive filter on code, display name, and description (e.g. "pm" for particulates, "ozone", "co"). The full catalog is small (~44 entries); omit to list everything. This filters the fetched list on our side — it is not an upstream search.
  • pollutantsOnlybooleanWhen true, exclude meteorological/auxiliary parameters (temperature, humidity, wind, pressure, particle-count channels) and return only air pollutants. Default false (full catalog).
openaq_list_countries1 param

Catalog of country-level coverage: id, ISO code, name, the date span of available station data (datetimeFirst/datetimeLast), and which parameters are measured anywhere in that country. The availability check before a regional sweep — answers "which countries have NO2 monitoring?" and tells you whether a country has recent data before you call openaq_find_locations. Coverage is uneven worldwide; this surfaces where measured data exists.

  • querystringLocal case-insensitive filter on country code and name (e.g. "united", "IN", "germany"). The list is bounded (~153 countries); omit to list all. Filters the fetched list on our side, not an upstream search.
openaq_dataframe_query2 params

Run a read-only SQL SELECT against the measurement tables openaq_get_measurements staged on a DataCanvas. Reference tables by the name the measurements call returned (measurements_<sensorId>). For aggregation (monthly means, exceedance counts) and cross-sensor comparison over series too large to inline. Only SELECT is allowed — a four-layer gate rejects writes, DDL, and file/network table functions.

  • canvas_idstringrequiredDataCanvas id returned by openaq_get_measurements when a series spilled.
  • sqlstringrequiredRead-only SELECT. Reference tables by the names openaq_get_measurements returned (e.g. measurements_1701). Use openaq_dataframe_describe first to see table and column names.
openaq_dataframe_describe1 param

List the tables and columns staged on a DataCanvas so you can write valid SQL for openaq_dataframe_query without guessing column names. Returns each measurement table (measurements_<sensorId>) with its row count and column names. Throws canvas_unavailable when DuckDB is off.

  • canvas_idstringrequiredDataCanvas id returned by openaq_get_measurements when a series spilled.
README.md

@cyanheads/openaq-mcp-server

Find air-quality monitoring stations, read latest sensor values, and pull historical pollutant series via MCP. STDIO & Streamable HTTP.

7 Tools (2 opt-in) • 2 Resources

npm License Docker MCP SDK TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://openaq.caseyjhand.com/mcp


openaq-mcp-server wraps the OpenAQ v3 API to expose measured air quality — physical-sensor observations from government reference monitors and research-grade sensors worldwide. It is the ground-truth counterpart to a modeled air-quality grid: where a model gives a concentration anywhere, OpenAQ gives an actual reading from a physical monitor — sparser, unevenly distributed, but real.

Coverage is uneven and honest. An empty result means there is no monitoring there, not that the air is clean — every discovery tool says so, and points to a modeled fallback (open-meteo-mcp-server's air-quality tool) for anywhere-coverage.

Tools

Five domain tools cover the workflow — discover stations, read current values, pull history, and resolve the two catalogs (pollutant units, country coverage) — plus two DataCanvas tools for SQL over historical series too large to inline. The data model is location → sensor → parameter; the server hides the sensor layer so you think in stations and parameters, never sensor ids.

Tool Description
openaq_find_locations Find monitoring stations near a point, in a bounding box, or by country. The required first step — readings and measurements key on the location id this returns.
openaq_get_readings Latest measured value for every sensor at a station, each joined with its pollutant and unit. The current-conditions tool.
openaq_get_measurements Historical series for one pollutant at one station over a date range, with raw/hourly/daily aggregation. Large ranges spill to a DataCanvas.
openaq_list_parameters Catalog of measurable pollutants and their canonical units. The unit-disambiguation reference.
openaq_list_countries Catalog of country-level coverage — data span and parameters measured. An availability check before a regional sweep.
openaq_dataframe_describe List the tables and columns staged on a DataCanvas so you can write valid SQL.
openaq_dataframe_query Run a read-only SELECT over staged measurement series.

openaq_find_locations

Find air-quality monitoring stations (measured by physical sensors, not modeled) and the parameters each one reports.

  • Three search scopes — coordinates + radius (near-me), bbox (area sweep), or iso country code; at least one is required
  • radius is in metres, 1–25000 (the API hard-caps at 25000); larger areas need bbox, which returns no distance
  • parametersId narrows to stations that measure a given parameter (each returned station still lists all its sensors)
  • Returns each station's id, name, coordinates, distance (when searching by coordinates), country, provider, isMonitor/isMobile, the parameters its sensors measure with units, and the datetimeFirst/datetimeLast data span
  • Empty result means no coverage, not clean air — widen the radius, check openaq_list_countries, or fall back to the modeled open-meteo air-quality tool

openaq_get_readings

Latest value per sensor at a station — the current-conditions tool.

  • Pass a locationId from openaq_find_locations, or coordinates + parametersId to auto-resolve the nearest station (within 25km) that measures that parameter
  • The raw OpenAQ latest feed is keyed only by sensor id; this tool joins it against the station's sensor → parameter → unit map, so every value carries its pollutant and unit
  • With locationId, parametersId optionally filters the returned values to one parameter; omit it for all sensors
  • Each value carries its UTC and local timestamp plus the station's datetimeLast — recency varies by station, so "latest" may be minutes or hours old

openaq_get_measurements

Historical measurement series for one pollutant at one station over a date range — for trend analysis and "was last week worse than the monthly average?".

  • Pass a locationId and a parametersId; the tool resolves the station's sensor for that parameter internally (v3 series are sensor-scoped, but you think in stations)
  • aggregation: raw (every reported value), hourly, or dailyhourly/daily add a per-bucket statistical summary (min, median, max, mean, sd)
  • datetimeFrom/datetimeTo accept a date (YYYY-MM-DD) or full UTC timestamp (YYYY-MM-DDTHH:MM:SSZ); omit for the most recent values
  • Values carry their unit; the server never converts between µg/m³, ppm, and ppb (the conversion is gas- and temperature-dependent)
  • Large ranges spill to a DataCanvas — see below

DataCanvas spill workflow

A multi-month raw series can be thousands of rows — too large to inline without blowing context, and a fixed slice would blind the agent to the rest. When a series exceeds the inline preview (100 rows), openaq_get_measurements stages the full set on a DuckDB-backed DataCanvas and returns:

  • a preview (series, capped at 100 rows) plus rowCount and the totalCount enrichment,
  • truncated: true, canvasId, and a tableName of the form measurements_<sensorId>.

You then query the full set with the two consumer tools:

Tool Use
openaq_dataframe_describe List staged tables and their columns (value, datetimeFrom, datetimeTo, min, median, max, avg, sd, percentComplete, flagged) — call this first to write SQL without guessing names.
openaq_dataframe_query Run a read-only SELECT for monthly means, exceedance counts, percentiles, or cross-sensor comparisons.

Pass a prior canvas_id back into openaq_get_measurements to stage a second station's series on the same canvas (as measurements_<otherSensorId>), then JOIN/UNION the two in one query to compare stations.

Requires CANVAS_PROVIDER_TYPE=duckdb. Without it, openaq_get_measurements still returns the truncated preview plus a notice (it does not fail), and the two dataframe tools return a canvas_unavailable error directing you to enable DuckDB.

openaq_dataframe_query is read-only by design — a four-layer SQL gate rejects writes, DDL, and file/network table functions; only a single SELECT runs.

Resources and prompts

Type Name Description
Resource openaq://location/{locationId} Location metadata for a known location id — name, coordinates, country, provider, sensors (each with parameter + unit), and data span.
Resource openaq://parameters Full pollutant + unit catalog (same data as openaq_list_parameters).

All resource data is also reachable via tools — both resources mirror tool output, so tool-only MCP clients lose nothing. There are no prompts: this is a data-lookup domain with no recurring analysis template that earns one (a WHO-guideline health snapshot is a cross-server workflow, not localized here).

Features

Built on @cyanheads/mcp-ts-core:

  • Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
  • Unified error handling — handlers throw, framework catches, classifies, and formats
  • Typed error contracts per tool — each network tool declares reason/code/when/recovery, so failures carry a concrete next move
  • Pluggable auth (none, jwt, oauth) and structured, request-scoped logging with optional OpenTelemetry tracing
  • STDIO and Streamable HTTP transports from one codebase

OpenAQ-specific:

  • Single typed client over the OpenAQ v3 REST API with X-API-Key auth, retry with rate-limit-calibrated backoff, and OpenAQ-specific error classification (clean-JSON 404 → NotFound; the Python-repr 422 body → ValidationError; the plain-text 500 on bad coordinates → transient ServiceUnavailable)
  • Hides the v3 location → sensor → measurement hierarchy — openaq_get_measurements resolves a station + parameter to the underlying sensor; openaq_get_readings joins the latest feed against the sensor map so every value is labeled
  • DataCanvas spillover for large measurement series, queryable with read-only DuckDB SQL
  • Coordinates and radius are bounded in Zod at the edge — OpenAQ returns an opaque plain-text 500 for out-of-range input, so the server rejects it cleanly before the call

Agent-friendly output:

  • Measured-vs-modeled framing in every discovery tool — an empty result is stated as no coverage, not clean air, with a pointer to the modeled fallback, so an agent never misreads sparse data as a clean reading
  • Units travel with every value, never converted — the same pollutant has multiple parameter ids for different units (co is id 4 µg/m³, id 8 ppm, id 102 ppb), so parametersId is the precise selector and openaq_list_parameters maps pollutant + unit → id
  • Chainable ids and staleness signals — location id → readings/measurements, sensor id → history; datetimeLast and per-value timestamps expose how fresh "latest" actually is
  • Capped lists disclose truncation (totalCount, truncated) via framework enrichment, reaching both the structured and text output surfaces

Getting started

Public Hosted Instance

A public instance is available at https://openaq.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP, with this client config:

{
  "mcpServers": {
    "openaq-mcp-server": {
      "type": "streamable-http",
      "url": "https://openaq.caseyjhand.com/mcp"
    }
  }
}

Self-hosted

An OpenAQ v3 API key is required — sent as the X-API-Key header on every request. Get a free key from your OpenAQ Explorer account.

Add the following to your MCP client configuration file.

{
  "mcpServers": {
    "openaq-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/openaq-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "OPENAQ_API_KEY": "your-api-key"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "openaq-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/openaq-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "OPENAQ_API_KEY": "your-api-key"
      }
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "openaq-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "MCP_TRANSPORT_TYPE=stdio",
        "-e", "OPENAQ_API_KEY=your-api-key",
        "ghcr.io/cyanheads/openaq-mcp-server:latest"
      ]
    }
  }
}

To enable DataCanvas SQL over large measurement series, add "CANVAS_PROVIDER_TYPE": "duckdb" to env.

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 OPENAQ_API_KEY=your-api-key bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/openaq-mcp-server.git
  1. Navigate into the directory:
cd openaq-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment:
cp .env.example .env
# edit .env and set OPENAQ_API_KEY

Configuration

All configuration is validated at startup via Zod schemas. Key environment variables:

Variable Description Default
OPENAQ_API_KEY Required. OpenAQ v3 API key, sent as the X-API-Key header. A missing key surfaces as a clean startup error.
OPENAQ_API_BASE_URL OpenAQ v3 API base URL. Override for a proxy or test mirror. https://api.openaq.org/v3
CANVAS_PROVIDER_TYPE Set to duckdb to enable DataCanvas SQL over large measurement series. Without it, large series return a truncated preview and the dataframe tools are inert. none
MCP_TRANSPORT_TYPE Transport: stdio or http. stdio
MCP_HTTP_PORT Port for the HTTP server. 3010
MCP_AUTH_MODE Auth mode: none, jwt, or oauth. none
MCP_LOG_LEVEL Log level (RFC 5424). info
LOGS_DIR Directory for log files (Node.js only). <project-root>/logs

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    bun run rebuild
    bun run start:http   # or start:stdio
    
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security, changelog sync
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

docker build -t openaq-mcp-server .
docker run --rm -e OPENAQ_API_KEY=your-api-key -p 3010:3010 openaq-mcp-server

The image defaults to HTTP transport, stateless session mode, and logs to /var/log/openaq-mcp-server. The @duckdb/node-api runtime dependency ships in the image, so DataCanvas works once CANVAS_PROVIDER_TYPE=duckdb is set. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

Directory Purpose
src/index.ts createApp() entry point — registers tools/resources and inits the service + canvas.
src/config Server-specific environment variable parsing and validation with Zod.
src/mcp-server/tools/definitions Tool definitions (*.tool.ts) — five OpenAQ tools plus two dataframe_* tools.
src/mcp-server/resources/definitions Resource definitions (*.resource.ts) — location and parameters mirrors.
src/services/openaq OpenAQ v3 API client, request/auth/retry, and domain types.
tests/ Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools and resources in the createApp() arrays
  • Wrap the OpenAQ API: validate raw → normalize to the domain type → return the output schema; surface units verbatim and never fabricate missing fields

Data & licensing

Air quality data served by this MCP server is sourced from the OpenAQ platform. Attribution to OpenAQ as the data source is required when using this server's output (OpenAQ Terms of Use).

OpenAQ aggregates measurements from hundreds of government agencies, research institutions, and other monitoring networks worldwide. Each of those upstream providers may publish its own attribution or licensing terms. The provider field returned by openaq_find_locations, openaq_get_readings, and the openaq://location/{locationId} resource identifies the originating network for each station. Downstream users are responsible for reviewing and complying with the terms of any provider whose data they use.

Contributing

Issues and pull requests are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Recommended MCP Servers

FAQ

What does openaq mcp server MCP do?

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.

When should I use openaq mcp server?

User asks about Openaq Mcp Server mcp, find air-quality stations and read pollutant observations from governm.

Is this MCP server safe to install?

Review the Security Audits panel and upstream source before connecting in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.