Skip to main content

EpiDash

CI Coverage PyPI Python 3.12+ License: MIT

A light, MCP-controlled dashboard service. EpidBot (and any MCP client holding a control key) builds, updates, publishes and unpublishes dashboards through an MCP tool surface; anyone with a dashboard link views it in a minimal web UI. Viewers can ask EpidBot to tweak a dashboard from the browser.

Live instance: https://dash.kwar-ai.com.br

EpidBot agent ──(MCP client, dk_ key)──▶ EpiDash /mcp (FastMCP tools)
                                            │
                       SQLite ◀─────────────┤ (dashboards, charts, keys, audit)
                                            │
Viewer browser ──(public link)──▶ EpiDash / (vanilla JS + Plotly)
      │                                 │
      └── tweak request ──▶ POST /api/dashboards/{slug}/tweak
                                 │  (X-Internal-Request: shared secret)
                                 ▼
                           EpidBot /api/v1/chat ──agent──▶ EpiDash MCP tools

Quick start

uv sync
uv run uvicorn epidash.main:app --port 8000

Then issue a control key and talk to the MCP endpoint:

uv run python -m epidash.cli add-key "local"
# → dk_...  (shown once)
# The MCP endpoint is stateless: one POST per call, no handshake needed.
curl -s -X POST http://localhost:8000/mcp \
  -H "X-API-Key: dk_..." -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

In an MCP client (e.g. Claude Desktop, Cursor, EpidBot):

{
  "mcpServers": {
    "epidash": {
      "url": "http://localhost:8000/mcp",
      "headers": { "X-API-Key": "dk_..." }
    }
  }
}

Configuration (environment variables)

Variable Default Purpose
EPIDASH_DB_PATH data/epidash.db SQLite file (WAL)
EPIDASH_BASE_URL http://localhost:8000 Public base URL for share links
EPIDASH_MCP_PATH /mcp Mount path of the MCP endpoint
EPIDASH_ALLOWED_HOSTS localhost,127.0.0.1 Host header allowlist (DNS-rebinding protection)
EPIDASH_BOOTSTRAP_KEY — Pre-shared dk_ key seeded at startup (infra automation)
EPIDBOT_BASE_URL — EpidBot base URL; enables the tweak flow
EPIDBOT_INTERNAL_SECRET — Shared secret (EpidBot INTERNAL_API_SECRET) or ek_ API key
EPIDASH_TWEAK_RATE_LIMIT_PER_HOUR 20 Per-dashboard viewer tweak budget
EPIDASH_ASK_RATE_LIMIT_PER_HOUR 30 Per-dashboard viewer question budget
EPIDASH_TWEAK_TIMEOUT_S 300 Max wait for an EpidBot tweak turn
EPIDASH_OIDC_ISSUER — Keycloak issuer URL (enables viewer login)
EPIDASH_OIDC_CLIENT_ID / _SECRET — OIDC confidential client for EpiDash
EPIDASH_SESSION_SECRET — Long random string; signs viewer session cookies
EPIDASH_PG_DSN_<NAME> — Named Postgres DSN for postgres data sources

MCP tools

Read-only: list_dashboards, get_dashboard, get_view_stats, get_audit_log (filterable by action and dashboard_id).

Mutations (audited): create_dashboard, update_dashboard, publish_dashboard (confirm), unpublish_dashboard (confirm), delete_dashboard (confirm), add_chart, update_chart, remove_chart (confirm), duplicate_dashboard, style_charts, set_dashboard_access (confirm), create_share_token (confirm), list_share_tokens, revoke_share_token (confirm).

Live data sources

Charts can auto-update from remote data instead of holding a static snapshot. attach_data_source(chart_id, kind, ..., refresh_interval_s=300) attaches a source:

kind payload
url_json url (+ optional dotted path) — the payload is the chart's data envelope itself ({"columns": ..., "rows": ...})
url_csv url — header row + data rows (numeric cells auto-converted); feeds line/bar/pie/table charts
postgres source (a registered DSN name) + sql — query result columns/rows become the envelope; DSNs live in EPIDASH_PG_DSN_<NAME> env vars, never in the database

A background refresher fetches due sources every 30s (minimum interval 30s) and replaces the chart's data on success — viewers pick the change up on their next poll, and chart cards show an ⇣ auto badge with refresh status. Failures never lose existing data; inspect them with get_data_source(chart_id), force a run with refresh_data_source, or stop with remove_data_source (confirm).

Chart data envelopes

kind data
line, bar, pie, table {"columns": ["month", "cases"], "rows": [["Jan", 120], ...]}
kpi {"value": 1234, "label": "Cases", "unit": "", "delta": "+12%"}
choropleth {"values": {"SP": 120, "RJ": 90}, "region_level": "uf", "label": "cases"}
markdown {"text": "## Notes\nNarrative **between** charts"} — rendered as a narrative block

spec accepts style overrides ({"color": "#4ade80"}, {"y_label": "cases"}) and position controls ordering/width ({"order": 1, "width": "half"}).

Viewer login & access control

Optionally, viewers can sign in through Keycloak (OIDC). Configure EPIDASH_OIDC_ISSUER, EPIDASH_OIDC_CLIENT_ID, EPIDASH_OIDC_CLIENT_SECRET and EPIDASH_SESSION_SECRET to enable it — without them, EpiDash stays fully anonymous.

With login enabled:

  • logged-in viewers get server-side like dedupe and their questions are attributed ([Asked by name] reaches EpidBot with the question);
  • dashboards gain a visibility level, set via the set_dashboard_access MCP tool: public (gallery + link), unlisted (link only), restricted (logged-in viewers or share tokens);
  • create_share_token mints expiring links (https://…/?t=st_…#/d/slug) that work even for restricted dashboards; revoke_share_token kills them.

The UI is available in English and pt-BR (?lang=pt-BR, persisted).

Viewer tweak flow

A viewer types a request ("show only the Southeast after 2020") into the dashboard's chat box. EpiDash forwards it as an EpidBot chat turn with the dashboard context; the EpidBot agent uses its EpiDash MCP tools to update the charts; the viewer's browser picks up the change on its next poll.

Viewers can also ask questions ("why did cases spike in March?") via the Ask button — the same chat pipeline, but the prompt instructs EpidBot to answer from the dashboard data without modifying it. No authentication is required for either; questions are recorded in the tweaks table with kind='question' and rate-limited separately from tweaks.

Requires EPIDBOT_BASE_URL + EPIDBOT_INTERNAL_SECRET (EpidBot must accept X-Internal-Request on its chat endpoint — see the EpidBot-side integration docs) or an ek_ API key.

Deployment

Single container, single volume:

docker compose up -d --build

Production (KwarAI) deploys this via the Ansible epidash role behind nginx at dash.kwar-ai.com.br — see the infrastructure repo.

Development

uv sync                  # deps
uv run pytest -v         # tests
uv run ty check epidash/ # type check

Browser end-to-end tests (headless chromium):

uv sync --group e2e
uv run playwright install chromium
uv run pytest tests_e2e -m e2e -v

License

MIT

Metadata

Release files for epidash 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for epidash 0.4.0
File Size Uploaded Provenance
epidash-0.4.0.tar.gz 1.9 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for epidash 0.4.0
File Interpreter ABI Platform
epidash-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.6 MB

Release files / epidash-0.4.0.tar.gz

Download URL epidash-0.4.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
ff8aceda7e98f40a160448b92f75aeebe1f4c9384ab7a8f8a3a11f3997f32add
BLAKE2b-256 checksum
How to use checksums
f7d89932db31041cb0603fbec36afb962a060829dad880f1d64d6a5d33272e6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / epidash-0.4.0-py3-none-any.whl

Download URL epidash-0.4.0-py3-none-any.whl
Size 1.8 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0907e5ee03af5dd208027cbc41c5081d267d250125e3a78a2683bba493bd49c6
BLAKE2b-256 checksum
How to use checksums
2432394d8676be4fad178447ff5bdba4bf44f56ed7b6ca2bbb79304281635ce9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for epidash 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for epidash-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page