Skip to main content
Glama
nguyenminhduc9988

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: MIT](https://img.shields.io/badge/license-MIT-10b981.svg)](LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-22d3ee.svg)](#contributing)
[![Dependencies](https://img.shields.io/badge/npm%20dependencies-zero-0a0a0f.svg)](package.json)
[![Build](https://img.shields.io/badge/build%20step-none-64748b.svg)](#architecture)

![SITUROOM dashboard — dark ops world map with live quakes, events, tension heat, headlines and markets](docs/hero.png)

**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).

  ![Tension index panel — ranked country bars](docs/tension.png)

- **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.

![Settings dialog — BYOK endpoint, watchlist, language toggle](docs/settings.png)

## 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