SITUROOM MCP Server
README.md
# SITUROOM
**The zero-backend global situation room — real-time earthquakes, conflicts, hazards and markets in one static page. No server. No API keys. No paywall.**
[](LICENSE)
[](#contributing)
[](package.json)
[](#architecture)

**Live demo:** https://nguyenminhduc9988.github.io/situroom/
---
## Why
Most "world monitor" intel dashboards are hosted products: a big server fleet renders the picture, the scoring is a black box, and the useful parts (APIs, agent access) sit behind a paid key. SITUROOM is the opposite bet — everything a situation dashboard needs is available from free, keyless, CORS-friendly public APIs, so the entire product can be **one static folder**.
| | SITUROOM | Typical hosted intel dashboards |
|---|---|---|
| **License** | MIT, no commercial tier | AGPL + commercial-license upsell |
| **Backend** | none — static files | fleets of edge functions + Redis + relays |
| **API / agent access** | free local MCP server, included | paid API keys |
| **Scoring** | documented client-side formula ([js/score.js](js/score.js)) — recompute it in devtools | opaque, server-side |
| **Offline** | PWA + committed data snapshot fallback | online-only |
| **Setup** | open one URL, or `python3 -m http.server` | cloud deployment |
Six free sources today, [PRs for more welcome](#contributing).
## Quick start
**1. Just open it** — https://nguyenminhduc9988.github.io/situroom/
**2. Run it locally** (any static server works; no install, no build):
```bash
git clone https://github.com/nguyenminhduc9988/situroom
cd situroom
python3 -m http.server 8080
# open http://localhost:8080
```
**3. Host your own** — fork the repo, enable GitHub Pages (deploy from `main`, root). Done: it's static files.
## Features
- **Canvas world map** — vendored d3-geo projection, graticule, country hover, magnitude-scaled quake markers, category-colored natural events, tension heat blobs. Toggleable layers, pulse animations (disabled under `prefers-reduced-motion`).
- **Tension index** — transparent per-country score over the current news window, top-10 bars colored green→amber→red. The formula is documented [below](#the-tension-index-formula) and in the UI ("formula?" button).

- **Headlines** — conflict-flavored world news from GDELT (English sources by default, all-languages toggle in settings).
- **Quakes / Events / Tech panels** — USGS earthquakes, NASA EONET natural events, Hacker News front page.
- **Markets ticker** — BTC/ETH/SOL with 24h change, USD FX reference rates.
- **Status chips per source** — LIVE / STALE (cached) / SNAPSHOT / ERROR, so you always know what you're looking at.
- **Watchlist** — pin countries you care about to the top of the tension board.
- **Responsive** — panels stack on narrow viewports; the six status chips collapse into one aggregate chip.
<img src="docs/mobile.png" alt="SITUROOM on a phone-sized viewport" width="320">
## Data sources
All free, keyless, fetched directly from your browser (CORS-friendly). Attribution and thanks:
| Source | Data | Refresh |
|---|---|---|
| [USGS Earthquake Hazards Program](https://earthquake.usgs.gov/) | earthquakes, past 24h | 5 min |
| [NASA EONET](https://eonet.gsfc.nasa.gov/) | open natural events (wildfires, storms, volcanoes…) | 10 min |
| [GDELT Project](https://www.gdeltproject.org/) DOC 2.0 | conflict/military/sanctions/protest headlines, ~6h window | 15 min |
| [CoinGecko](https://www.coingecko.com/) | BTC / ETH / SOL spot + 24h change | 2 min |
| [Frankfurter](https://frankfurter.dev/) | USD→EUR/JPY/GBP/CNY reference rates | 60 min |
| [HN Algolia](https://hn.algolia.com/api) | Hacker News front page | 10 min |
Every source degrades independently: live fetch → last good response from localStorage (**STALE**) → committed capture in [`data/snapshot.json`](data/snapshot.json) (**SNAPSHOT**) → **ERROR** chip. The dashboard never renders blank.
## The tension index formula
No black box — the entire computation runs in your browser over the raw GDELT article list, in [`js/score.js`](js/score.js):
- **volume(C)** — articles published in country C or naming C (or a common alias) in the headline
- **severity(C)** — mean headline keyword weight: `3` war/strike/attack/missile…, `2` sanctions/military/nuclear…, `1` protest/unrest/clashes…, floor `0.5`
- **score(C)** = `100 × norm(0.6·log1p(volume) + 0.4·severity)` — the hottest country of the moment reads 100, everything else is relative
Honesty note: GDELT's article-list API doesn't expose its per-article tone field, so severity is a documented keyword proxy — cruder than GDELT tone, but fully auditable, which is the point. The full country/alias gazetteer and keyword table are plain data at the top of the file; the unit tests in [`test/score.test.js`](test/score.test.js) pin the behavior.
## AI sitrep (optional, bring your own key)
Off by default and never required. If you point settings (gear icon) at any OpenAI-compatible endpoint — including a local Ollama at `http://localhost:11434/v1` — an **AI SITREP** button appears that summarizes the current headlines + tension board into a 5-bullet brief. Your endpoint, key and model live **only in your browser's localStorage** and are sent nowhere except the endpoint you configured.

## MCP server (agents get the feeds for free)
[`mcp/situroom_mcp.py`](mcp/situroom_mcp.py) is a zero-dependency Python 3.10+ stdio MCP server exposing the same feeds as tools: `get_quakes`, `get_events`, `get_headlines`, `get_tension`, `get_markets` — normalized to the web app's shapes, tension formula faithfully ported from `js/score.js`. Config snippets for Claude Desktop/Code and Cursor in [`mcp/README.md`](mcp/README.md).
```bash
claude mcp add situroom -- python3 /path/to/situroom/mcp/situroom_mcp.py
```
## Architecture
Vanilla ES modules. No framework, no build step, no npm install. The only third-party code is vendored and committed: d3 v7 + topojson-client (plus the world-atlas 110m topology) in [`vendor/`](vendor/), so the app runs with zero network access to any CDN.
```
index.html
├── css/app.css dark ops theme (system fonts only)
├── js/app.js boot, source registry, 60s scheduler, panels
│ ├── js/map.js canvas map (offscreen-cached base + marker layer)
│ ├── js/score.js tension index (pure, documented, tested)
│ ├── js/store.js localStorage cache + settings
│ ├── js/brief.js optional BYOK sitrep
│ └── js/sources/*.js one module per feed: fetchX() + pure normalizeX()
├── sw.js PWA: precached shell, network-first API fallback
└── data/snapshot.json committed real capture — the floor of the chain:
live fetch ──fail──▶ localStorage last-good ──miss──▶ committed snapshot ──miss──▶ ERROR chip
(LIVE) (STALE) (SNAPSHOT)
```
Run the tests (pure functions only, fully offline):
```bash
node --test test/*.test.js # or: npm test — 44 tests, zero dependencies
```
## Contributing
PRs welcome — especially new **free, keyless, CORS-friendly** data sources. The bar for a source module: one file in `js/sources/`, export a pure `normalizeX()` (unit-tested against a captured payload in `data/snapshot.json`) and a `fetchX()` that returns `null` on any failure. Open an issue first for anything bigger than a source or a fix.
## License
[MIT](LICENSE) © 2026 Minh-Duc Nguyen
---
Built by [BigWinner.work](https://bigwinner.work) — AI Tools & Consulting
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues