A hybrid-intelligence environmental crisis monitor, built on the Model Context Protocol (MCP)
EcoSentinel connects AI assistants like Claude to real-time environmental data from three global sources (live air quality sensors, satellite wildfire detection, and weather forecasting) through the Model Context Protocol. It goes a step further than simple data-fetching: it statistically detects when air quality is abnormal for a given city, and is being built out to mathematically attribute why, separating "there's a wildfire nearby" from "the wildfire is actually causing this spike."
Ask your AI:
- "Is the air safe to breathe in Delhi today?"
- "Are there wildfires near Sydney right now?"
- "Is Jakarta's air quality unusual for this time of year, or just normal smog?"
- "Give me a full environmental crisis briefing for Mumbai."
- Why "Hybrid Intelligence"
- Architecture
- How a Request Flows Through the System
- What Is MCP, in Plain English
- Tools
- Statistical Anomaly Detection
- Causal Inference Engine
- Getting Started
- Example Output
- Data Sources
- Project Structure
- Running Tests
- License
- Acknowledgements
Most AI projects let the language model do all of the reasoning, including the math. That's risky, because LLMs hallucinate numbers.
EcoSentinel enforces a strict separation between two kinds of intelligence working together:
- Language model intelligence: Claude handles natural-language understanding, decides which tools to call and when, and synthesizes the results into a clear answer for the user.
- Statistical / mathematical intelligence: Python libraries (
scipy,numpy, and eventuallyDoWhy) handle the actual numerical computation, with zero hallucination risk.
Claude decides what to analyze; Python computes how. When the system flags a pollution spike near an active wildfire, it doesn't let Claude guess whether the fire is the cause. It runs real statistics and causal inference in Python and hands Claude a precise, defensible number.
EcoSentinel is organized into six cooperating layers:
| Layer | Name | Responsibility | Technology |
|---|---|---|---|
| 1 | Data Collection | Fetch live data from three APIs concurrently | httpx, asyncio.gather, OpenAQ, NASA FIRMS, Open-Meteo |
| 2 | Statistical Engine | Z-score anomaly detection on 30-day historical baselines | Python, scipy, numpy |
| 3 | Causal Inference | Compute P(Y | do(X)), attributing pollution cause via a DAG |
DoWhy, pandas |
| 4 | Agentic Router | Conditionally trigger deeper analysis only when an anomaly is confirmed | async Python in server.py |
| 5 | MCP Interface | Expose every tool to Claude via the Model Context Protocol | FastMCP, mcp |
| 6 | Visual Dashboard | Live geospatial maps, wind vectors, time-series charts | Streamlit, plotly, folium |
ββββββββββββ MCP βββββββββββββββββββββββ
β Claude β ββββββββββββββββββΊ β EcoSentinel Server β
β (the AI) β (tool calls) β (server.py) β
ββββββββββββ ββββββββββββ¬βββββββββββ
β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
β OpenAQ API β β NASA FIRMS API β β Open-Meteo API β
β Air quality β β Wildfire hotspotsβ β Weather/wind β
βββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
β β β
ββββββββββββββββ¬ββββββββββββββ΄βββββββββββββββ¬ββββββββββββββ
βΌ βΌ
ββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
β Z-Score Anomaly Engine ββββΆβ Causal Inference Engine β
β (scipy / numpy) β β (DoWhy DAG attribution) β
ββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
A typical question moves through the layers like this:
- A user asks Claude something like "What's the air quality in Lagos, and is it normal?"
- Claude inspects the tools EcoSentinel exposes and decides which to call.
- The agentic router kicks off concurrent calls to all relevant data sources.
asyncio.gather()fetches air quality, wildfire, and weather data simultaneously, roughly a 3x speedup over sequential calls.- The z-score engine compares today's PM2.5 reading to the city's 30-day historical baseline.
- If the z-score crosses the anomaly threshold (
z β₯ 2.0), the router automatically triggers the causal inference engine. - The causal engine constructs a DAG and computes
P(Y | do(X))via the backdoor criterion to estimate how much of the spike is attributable to a nearby wildfire, after controlling for wind. - If there isn't enough historical data for a confident causal estimate, the system gracefully degrades, falling back to the z-score result alone rather than guessing.
- Every result is returned to Claude as a structured report.
- Claude synthesizes everything into a clear, natural-language answer that's grounded entirely in real numbers, not invented ones.
This conditional, decision-driven behavior (fetch, evaluate, and only escalate when the evidence warrants it) is what makes the router agentic rather than a simple fixed pipeline.
MCP (Model Context Protocol) is an open standard, created by Anthropic, that defines how AI assistants talk to external tools and data sources.
Think of it like this: Claude is a brilliant expert sitting in a room with no
internet connection. MCP is a slot in the door. You slide a question through;
a program on the other side (server.py) fetches the real answer, runs the
math, and slides the result back. That's the difference between "Claude can
only repeat what it learned during training" and "Claude can tell you the
actual AQI in Mumbai right now, and whether that's unusual."
EcoSentinel is a custom MCP server: it exposes a set of tools that Claude can call whenever a user's question calls for live environmental data or statistical analysis.
| Tool | Parameters | Source | Status |
|---|---|---|---|
get_air_quality |
city |
OpenAQ v3 sensor measurements API | β Live |
get_wildfires |
city, radius_km, days |
NASA FIRMS (VIIRS SNPP) | β Live |
get_weather_risk |
city |
Open-Meteo forecast API | β Live |
get_crisis_summary |
city |
All three sources, fetched concurrently | β Live |
detect_anomaly |
city |
OpenAQ historical API + scipy z-score analysis |
π¬ In development |
get_causal_attribution |
city |
All sources + DoWhy causal engine |
π§ Planned |
Every live tool returns a clean, structured report: AQI calculated from raw pollutant readings, fire hotspots filtered by distance and confidence, and flood/storm risk derived from rainfall and wind-gust thresholds, so Claude always answers with real numbers, source citations, and timestamps.
Raw numbers lack context. An AQI of 120 might be an ordinary Tuesday in Delhi but a major emergency in Sydney. EcoSentinel adds that missing context with a z-score computed against each city's own 30-day history:
z = (current_value - mean) / standard_deviation
current_value β today's PM2.5 reading from OpenAQ
mean β average PM2.5 over the last 30 days for this city
std deviation β how much PM2.5 typically varies day to day
| Z-score | Meaning |
|---|---|
z < 1.5 |
Normal, no further analysis triggered |
1.5 β€ z < 2.0 |
Elevated, alert issued |
z β₯ 2.0 |
Anomaly confirmed, the agentic router escalates to the causal engine |
The engine fetches 30 days of historical PM2.5 averages, computes the mean and
standard deviation with numpy/scipy, derives the z-score, and returns a
structured result (z_score, mean, std, severity, is_anomaly, β¦) that
Claude can reason over directly.
Knowing that there's a wildfire and high PM2.5 near a city doesn't prove the
wildfire is the cause. Wind direction matters. If the wind is blowing away
from the city, the fire can't be responsible, no matter how close it is.
EcoSentinel uses DoWhy to mathematically isolate the wildfire's causal
contribution after controlling for wind: the actual difference between
correlation and causation.
Wildfire proximity (X) ββcausesβββΆ PM2.5 spike (Y)
Wind vectors (W) ββcausesβββΆ PM2.5 spike (Y)
Wind vectors (W) ββcausesβββΆ Wildfire smoke reaching the city
DoWhy reads this Directed Acyclic Graph and computes:
P(Y | do(X)) = the probability that PM2.5 is high BECAUSE of the wildfire,
after mathematically removing the effect of wind (the confounder)
| Variable | Role | Represents | Source |
|---|---|---|---|
X (Treatment) |
Wildfire proximity | Distance & thermal intensity of the nearest hotspot | NASA FIRMS |
Y (Outcome) |
PM2.5 reading | Current fine-particulate concentration | OpenAQ |
Wβ (Confounder) |
Wind vectors | Speed & direction carrying smoke toward/away from the city | Open-Meteo |
Wβ (Confounder) |
Historical baseline | 30-day average PM2.5 for the city | OpenAQ |
P(Y\|X): Given that we observe a wildfire, what is PM2.5? This includes all sorts of indirect, confounded effects.P(Y\|do(X)): If we forced a wildfire to exist and controlled for everything else, what would PM2.5 be? This isolates the true causal effect.
DoWhy identifies and adjusts for confounders using the backdoor
criterion, producing a single number (for example 0.73, meaning the
wildfire explains roughly 73% of this pollution spike) that is mathematically
computed, not estimated by an LLM. If there isn't enough data for a confident
estimate, the system gracefully falls back to the z-score result alone.
git clone https://github.com/maahipatel05/ecosentinel.git
cd ecosentinelpython -m venv venv
source venv/bin/activatepip install -r requirements.txtcp .env.example .envThen edit .env:
- Get a free NASA FIRMS key at https://firms.modaps.eosdis.nasa.gov/api/area/
and add it as
NASA_FIRMS_API_KEY. - Air quality (
OpenAQ) and weather (Open-Meteo) tools work out of the box with no API key required (an OpenAQ key just raises your rate limits).
Add EcoSentinel to your Claude Desktop config, found at:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"ecosentinel": {
"command": "python",
"args": ["/absolute/path/to/ecosentinel/src/server.py"]
}
}
}Restart Claude Desktop, and EcoSentinel will appear in the tools panel, ready to answer questions about any location on Earth.
Asking Claude for a crisis summary triggers get_crisis_summary, which fans
out to all three data sources simultaneously and returns something like:
# π EcoSentinel Crisis Briefing: Jakarta
*Generated: 2026-06-08 14:32 UTC | Powered by EcoSentinel MCP*
---
[Air Quality report: AQI, PM2.5, PM10, NO2, health guidance]
---
[Wildfire report: active hotspots, distance, confidence]
---
[Weather & flood risk report: rainfall, wind, 3-day outlook]
---
## π Summary
Data sources: OpenAQ Β· NASA FIRMS Β· Open-Meteo
For emergencies, always contact local authorities.
Running everything concurrently with asyncio.gather() means a full briefing
that would take roughly 9 seconds sequentially (3 APIs Γ 3 seconds each) comes
back in about 3, roughly a 3x speedup, for free.
- OpenAQ: Real-time air quality from a global network of ground-level sensors (PM2.5, PM10, NO2, CO, O3, SO2).
- NASA FIRMS: Near real-time satellite wildfire detection via the VIIRS SNPP sensor.
- Open-Meteo: Free weather forecasts, precipitation, and wind data, no API key required.
ecosentinel/
βββ src/
β βββ server.py # MCP server: tool definitions and agentic router
β βββ anamoly.py # Z-score anomaly detection engine (scipy/numpy)
β βββ causal.py # DoWhy causal inference engine: DAG + P(Y|do(X)) [planned]
βββ tests/
β βββ test_tools.py # Async unit tests for each live tool
β βββ test_anomaly.py # Unit tests verifying z-score math
βββ docs/ # Additional documentation
βββ .env.example # Template for required environment variables
βββ requirements.txt # Python dependencies
βββ LICENSE # MIT license
βββ README.md
python -m pytest tests/ -vThe test suite covers geocoding, air quality, wildfire, weather-risk, and anomaly-detection logic against both valid cities and invalid/unknown locations.
MIT. See LICENSE for details.
- Anthropic: for the Model Context Protocol
- OpenAQ: for open, global air quality data
- NASA FIRMS: for satellite-based wildfire detection
- Open-Meteo: for free, no-key-required weather data
- Microsoft DoWhy: for making rigorous causal inference accessible in Python
Built by Maahi Patel, Rice University. For emergencies, always contact local authorities. EcoSentinel is a tool for awareness and research, not a substitute for official alerts.