Openaq Mcp Server

io.github.cyanheadsv0.4.1更新於 Oct 8, 2026

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

已驗證Streamable HTTP可網頁執行Data & AnalyticsLocation & Lifestyle

概覽

AI 產生的概覽

讓助理尋找空氣品質監測站,並讀取 OpenAQ v3 網路的當前與歷史污染物量測資料。

功能
封裝 OpenAQ v3 API,讓助理能依座標、邊界框或國家搜尋監測站,讀取測站各感測器的最新數值,並以原始、每小時或每日彙總方式取得歷史污染物序列。輔助工具可列出含標準單位的污染物目錄以及各國涵蓋情形。較大的序列可暫存到 DataCanvas 進行唯讀 SQL 查詢,另有兩個資源分別對應測站中介資料與參數目錄。
適用情境
適合助理需要實測空氣品質資料時:尋找附近的政府或研究級監測站、查看當前污染物濃度,或分析某測站某污染物的歷史序列。也適合回答某國或某測站回報哪些污染物及其單位的問題。
執行需求
需要一組免費的 OpenAQ v3 API 金鑰,透過 OPENAQ_API_KEY 環境變數提供,並以 X-API-Key 標頭送出。可透過 npm/npx(Node.js v24+ 或 Bun v1.4.0+)或 Docker 在本機執行,也可使用公開託管的 Streamable HTTP 端點。啟用 DuckDB 暫存以進行 SQL 查詢需設定 CANVAS_PROVIDER_TYPE=duckdb。需要連線至 OpenAQ API 的網路存取。
安裝前請注意
OpenAQ API 金鑰屬於憑證,應避免放入共用設定。選用的 openaq_dataframe_drop 工具會刪除整個已暫存畫布及其所有資料表,預設關閉,需設定 OPENAQ_ENABLE_CANVAS_DROP 才會啟用;在關閉驗證時呼叫方共用同一租戶,啟用該工具後任何持有畫布 id 的人都能刪除它。失敗呼叫的酬載記錄可能寫下依欄位名稱遮蔽無法涵蓋的自由文字值。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Openaq Mcp Server,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

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

README

@cyanheads/openaq-mcp-server

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

8 Tools (7 enabled by default) • 2 Resources

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


Overview

Measured air quality from the OpenAQ v3 API: physical-sensor observations from government reference monitors and research-grade sensors worldwide. Find monitoring stations, read current values, and pull historical pollutant series from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

ToolDescription
openaq_find_locationsFind monitoring stations near a point, in a bounding box, or by country; the location ids the other tools take
openaq_get_readingsLatest value for every sensor at a station, labeled with its pollutant and unit
openaq_get_measurementsHistorical series for one pollutant at one station, raw or rolled up hourly/daily
openaq_list_parametersPollutant catalog with canonical units; maps a pollutant and unit to its parameter id
openaq_list_countriesCountry coverage: data span and the parameters measured in each
openaq_dataframe_describeList the tables and columns staged on a DataCanvas
openaq_dataframe_queryRun a read-only SQL SELECT over staged measurement series
openaq_dataframe_dropDelete a complete staged canvas; opt-in with OPENAQ_ENABLE_CANVAS_DROP=true

Resources

ResourceDescription
openaq://location/{locationId}Metadata for one station: coordinates, provider, sensors with units, data span
openaq://parametersFull pollutant and unit catalog

Both resources mirror tool output, so tool-only clients lose nothing.

Capability reference

openaq_find_locations tool

  • Scope by coordinates with radius (metres, 1–25000, default 12000), bbox, and/or iso (a code from openaq_list_countries); at least one is required, and coordinates and bbox exclude each other. parametersId, monitor, mobile, and providersId narrow further; up to 100 stations per page (limit, default 20), 1-based page
  • Each station carries id, distanceMeters (coordinate search only), provider/providerId, isMonitor/isMobile, its parameters with units, and the datetimeFirst/datetimeLast span; coordinates, country, and provider are null when OpenAQ lists none
  • totalCount counts stations through the current page and is a floor when totalCountIsLowerBound is set; no match fails as no_locations_found, a page past the end as page_exhausted

openaq_get_readings tool

  • A locationId, or coordinates plus parametersId to resolve the nearest station within 25 km that measures it (compared across up to 1,000 matching stations; a full 1,000 adds a notice); with locationId, parametersId optionally filters to one parameter
  • One reading per sensor with value, unit, sensorId, and datetimeUtc/datetimeLocal, plus the station's provider, providerId, timezone, and datetimeLast
  • Misses are typed: location_not_found, parameter_not_at_location (with the station's available ids), no_station_near_coordinates, no_recent_values

openaq_get_measurements tool

  • Requires locationId and parametersId; datetimeFrom/datetimeTo accept UTC timestamps or station-local YYYY-MM-DD dates (UTC days when the station has no timezone). aggregation is raw (default), hourly, or daily; up to 5,000 rows per call
  • Returns series, sensorId, pulledCount, pullComplete, effectiveRange, and station provider, providerId, and timezone. totalCount is a floor when totalCountIsLowerBound is set; rollups add summary statistics, gapCount, the first 20 missing intervals as gaps, and a notice when edge buckets are clipped
  • Past 100 rows, series is a preview (truncated) and the pulled rows stage on a DataCanvas as measurements_<sensorId> (canvasId, tableName) when CANVAS_PROVIDER_TYPE=duckdb; a supplied canvas_id stages onto that canvas at any size. One table per sensor: a second sensor adds a table to join against, and re-staging the same sensor overwrites its earlier series

openaq_list_parameters tool

  • Optional case-insensitive query over code, display name, and description; pollutantsOnly drops meteorological and particle-count channels
  • Rows carry id, name, displayName, unit, and description; one pollutant can appear under several ids by unit (CO is 4 in µg/m³, 8 in ppm, 102 in ppb)

openaq_list_countries tool

  • Optional query (a two-letter value matches an ISO 3166-1 alpha-2 code first; otherwise code or name substrings match) and parametersId; up to 100 per page (limit, default 20), 1-based page
  • Rows carry code (the iso value for openaq_find_locations), name, the datetimeFirst/datetimeLast span, and the parameters measured anywhere in the country; totalCount is the full filtered count

openaq_dataframe_describe tool

  • Takes a canvas_id from openaq_get_measurements and returns each staged table's name, rowCount, and columns. Call it before openaq_dataframe_query: the staged table is flat (min, sd) while the inline series nests those under summary
  • Fails as canvas_unavailable unless CANVAS_PROVIDER_TYPE=duckdb, or canvas_not_found for an unknown or expired id

openaq_dataframe_query tool

  • A canvas_id and one read-only SELECT; writes, DDL, and file/network table functions are rejected
  • At most 200 rows per response, with truncated set when the cap cut the result; page with ORDER BY plus LIMIT/OFFSET
  • Fails as canvas_unavailable, canvas_not_found, or missing_table

openaq_dataframe_drop tool

  • Registered only with OPENAQ_ENABLE_CANVAS_DROP=true; calls fail as canvas_unavailable unless CANVAS_PROVIDER_TYPE=duckdb. When disabled, the HTTP landing page shows the enable hint and tools/list omits the tool
  • Takes a canvas_id and deletes that whole canvas, including all staged tables. Other agents sharing the id lose access; OpenAQ's source data is unaffected
  • Returns the requested canvasId and dropped: true when deleted, false when no reachable canvas exists. With authentication off, callers share a tenant: anyone holding an id can delete its canvas when enabled

openaq://location/{locationId} resource

  • application/json: name, locality, timezone, country, provider, isMonitor/isMobile, coordinates, sensors (each with parameterId and unit), and the datetimeFirst/datetimeLast span
  • locationId comes from openaq_find_locations; cached 5 minutes

openaq://parameters resource

  • application/json catalog, the same rows as an unfiltered openaq_list_parameters
  • Cached 1 hour

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

OpenAQ-specific:

  • One typed client over the OpenAQ v3 REST API: X-API-Key auth, retry with backoff, and typed upstream failures (invalid_api_key, rate_limited, upstream_timeout, upstream_error) on every OpenAQ call
  • Hides the v3 location → sensor → measurement hierarchy: readings join the latest feed to the station's sensor map, and measurements resolve a station plus parameter to its sensor
  • Coordinates and bbox corners are range-checked and contradictory search scopes rejected before any request reaches OpenAQ
  • Large measurement series stage on a DataCanvas for read-only DuckDB SQL, one table per sensor, so two series on one canvas_id can be joined

Agent-friendly output:

  • Measured, not modeled: a search with no station fails as no_locations_found or no_station_near_coordinates, stated as no coverage rather than clean air
  • Units travel with every value and are never converted; parametersId selects pollutant and unit together
  • Freshness and truncation are explicit: per-value timestamps and datetimeLast, and totalCount / truncated / notice on capped results

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:

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

Self-Hosted / Local

Add the following to your MCP client configuration file.

json
{  "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):

json
{  "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:

json
{  "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"      ]    }  }}

Add "CANVAS_PROVIDER_TYPE": "duckdb" to env to enable DataCanvas SQL over large measurement series.

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

sh
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:
sh
git clone https://github.com/cyanheads/openaq-mcp-server.git
  1. Navigate into the directory:
sh
cd openaq-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
cp .env.example .env# edit .env and set OPENAQ_API_KEY

Configuration

VariableDescriptionDefault
OPENAQ_API_KEYRequired. OpenAQ v3 API key, sent as the X-API-Key header.—
OPENAQ_API_BASE_URLOpenAQ v3 API base URL, for a proxy or test mirror.https://api.openaq.org/v3
CANVAS_PROVIDER_TYPEduckdb stages large measurement series for SQL via the dataframe tools; without it, large series return a preview plus a notice. The .mcpb bundle ships without DuckDB, so use npm, npx, or Docker for canvas work.none
OPENAQ_ENABLE_CANVAS_DROPEnable openaq_dataframe_drop to delete entire canvases and release their resources. Requires DuckDB; an id shared with another agent grants deletion access within the same tenant.false
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTHTTP server port.3010
MCP_SESSION_MODEHTTP session mode: stateless, stateful, or auto. Overrides the server's explicit stateless posture; auto resolves to stateful.stateless
MCP_AUTH_MODEAuthentication: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (debug, info, warning, error, etc.).info
OTEL_ENABLEDEnable OpenTelemetry.false
OTEL_EXPORTER_OTLP_ENDPOINTOTLP base URL; traces use /v1/traces and metrics use /v1/metrics. Signal-specific endpoints override it.—
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTOpt into OTLP log export with a full endpoint URL. The base endpoint never enables logs.—
LOG_TOOL_FAILURE_PAYLOADSLog failed calls' arguments and results. Redacts by key name only; secrets in free-form values remain.false
LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTESUTF-8 byte cap per logged failure payload.16384

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

Running the server

Local development

  • Build and run:

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

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

Docker

sh
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. DuckDB 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

DirectoryPurpose
src/index.tscreateApp() entry point: registers tools and resources, wires the service and canvas.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (definitions/*.tool.ts) and shared input, error, and format helpers (shared/).
src/mcp-server/resourcesResource definitions (definitions/*.resource.ts).
src/servicesOpenAQ v3 client and domain types (openaq/), plus the DataCanvas accessor.
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 — catch only to map a failure to a declared error reason or to keep a partial result
  • 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

Contributing

Issues are welcome. Run checks and tests before submitting:

sh
bun run devcheckbun run test

License

Apache-2.0 — see LICENSE for details.

Data comes from the OpenAQ platform, and attribution to OpenAQ is required when using this server's output (Terms of Use). OpenAQ aggregates measurements from government agencies, research institutions, and other networks, each of which may set its own attribution or licensing terms. The provider field on openaq_find_locations, openaq_get_readings, and openaq_get_measurements results and the openaq://location/{locationId} resource names the originating network; review and follow the terms of any provider whose data you use.

來源:README.md,提交 0b9e44a

工具

0
工具後設資料尚未被收錄。

版本歷史

5
  1. v0.4.1最新Sep 27, 2026
  2. v0.3.0Sep 24, 2026
  3. v0.1.10Sep 22, 2026
  4. v0.1.9Sep 20, 2026
  5. v0.1.8Sep 16, 2026