No description
Find a file
kiania e571eeabed infra(db-roles): validated Option A — shared tracksolid_owner for migrators
Discovery (live) corrected the design: webhook_receiver, ingest_worker, and worker
all run run_migrations.py (DDL) and write telemetry — worker is the same image as
ingest_worker, not a reader. Because they ALTER objects they must own them, so all
three connect as the shared non-superuser tracksolid_owner (the role the repo already
intends to own these schemas). dashboard_api backend stays a reader (dashboard_app).

- app_roles_tracksolid_db.sql rewritten: tracksolid_owner LOGIN + CONNECTION LIMIT 30
  + GUCs + USAGE/CREATE; Timescale-aware ownership reassignment (skips table-linked
  sequences, ALTER MATERIALIZED VIEW for continuous aggregates, leaves reporting.v_trips
  with reporting_refresher, reassigns functions); dashboard_app read role.
- Reassignment validated in a rolled-back transaction on the live DB: reassigns the
  31-chunk position_history hypertable + the v_mileage_daily_cagg continuous aggregate,
  and as tracksolid_owner can ALTER the hypertable and create/drop tables.
- Runbook updated: discovery marked done, ownership folded into the apply (safe while
  apps still run as postgres — superuser bypasses ownership), corrected cutover order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 00:08:52 +03:00
docs docs(analytics-mcp): document tickets + fuel schemas and MCP_READABLE_SCHEMAS 2026-06-17 11:37:25 +03:00
scripts infra(db-roles): validated Option A — shared tracksolid_owner for migrators 2026-06-20 00:08:52 +03:00
.dockerignore feat: read-only Fleet Analytics MCP server 2026-06-16 23:43:24 +03:00
.gitignore feat: read-only Fleet Analytics MCP server 2026-06-16 23:43:24 +03:00
analytics_mcp.py feat(access): expose tickets + fuel schemas to analytics_ro (read-only) 2026-06-17 11:37:25 +03:00
deploy.sh feat(access): expose tickets + fuel schemas to analytics_ro (read-only) 2026-06-17 11:37:25 +03:00
Dockerfile feat: read-only Fleet Analytics MCP server 2026-06-16 23:43:24 +03:00
pyproject.toml feat: read-only Fleet Analytics MCP server 2026-06-16 23:43:24 +03:00
README.md fix: disable MCP DNS-rebinding host guard behind reverse proxy 2026-06-17 00:00:40 +03:00

Fleet Analytics MCP (read-only)

A read-only MCP server that lets the decision & analytics team query the Fireside fleet database (tracksolid_db — PostgreSQL 16 + TimescaleDB + PostGIS) directly from Claude — for reporting and decisions, never edit/delete.

It exposes a guarded general SELECT tool plus schema-introspection tools over the reporting.* (curated analytics layer) and tracksolid.* (raw telemetry) schemas, connecting as a dedicated least-privilege analytics_ro role. It is hosted on the same Coolify host as the database (the DB is internal-only and not reachable from a laptop), and authed with per-analyst Bearer tokens.

Sibling of the tracksolid_timescale_grafana_prod backend (the DB/ingestion stack) and the dashboard_api read bridge. This repo owns only the analytics MCP server and its analytics_ro role.

Read-only is enforced at four layers

  1. Role GRANTsanalytics_ro has only USAGE+SELECT on reporting/tracksolid and EXECUTE on reporting functions; no INSERT/UPDATE/DELETE, not the matview owner.
  2. default_transaction_read_only = on — set on the role and on every connection.
  3. Rolled-back transactions — every query runs in a txn that is rolled back, never committed.
  4. SQL guard — the query tool accepts a single SELECT/WITH statement only and rejects write/DDL keywords (clean errors instead of DB faults).

It deliberately does not import the backend's ts_shared_rev, so it carries none of the Tracksolid ingestion secrets — it needs only DATABASE_URL + MCP_AUTH_TOKENS.

Tools

Tool Purpose
query(sql, max_rows=1000) guarded read-only SELECT/WITH; auto-LIMIT; returns {row_count, truncated, rows}
list_schemas() readable schemas + object counts
list_tables(schema) tables + views in a schema
describe_table(schema, table) columns, types, nullability, defaults
list_functions(schema='reporting') reporting.fn_* signatures
sample_table(schema, table, n=20) first n rows (wrapper over query)

Layout

analytics_mcp.py                 # the MCP server (FastMCP streamable-HTTP; uvicorn target analytics_mcp:app)
Dockerfile                       # Coolify-buildable image (port 8892)
deploy.sh                        # manual host deploy (standalone Traefik bridge) — fallback to Coolify
scripts/analytics_ro_role.sql    # the read-only role DDL
scripts/bootstrap_analytics_ro.sh# host bootstrap: generate pw → apply role SQL
docs/ANALYTICS_MCP.md / .html    # full implementation guide + runbook

Deploy

The DB is internal-only, so the server runs on the same Coolify host as timescale_db.

0. Create the read-only role (once, on the host):

scp scripts/analytics_ro_role.sql scripts/bootstrap_analytics_ro.sh kianiadee@twala.rahamafresh.com:~/
ssh kianiadee@twala.rahamafresh.com 'bash ~/bootstrap_analytics_ro.sh'   # writes ~/.analytics_ro.pw (0600)

1a. Coolify-managed app (recommended): create a Coolify application from this repo (Forgejo repo.rahamafresh.com/kianiadee/fleetanalytics_mcp.git), Dockerfile build, app port 8892, attach the domain fleetmcp.rahamafresh.com (prod) / fleetmcp.fivetitude.com (staging). Set as secrets:

  • DATABASE_URL=postgresql://analytics_ro:<pw>@timescale_db:5432/tracksolid_db
  • MCP_AUTH_TOKENS=alice:<tok>,bob:<tok> (per-analyst)

Then connect the app to the network that can reach timescale_db (the tracksolid stack's network) so the timescale_db hostname resolves. Coolify manages the Traefik labels + TLS from the domain you set.

Optional env (sensible defaults):

  • MCP_MAX_ROWS (default 10000) — hard ceiling on rows a query may return.
  • MCP_DNS_REBINDING_PROTECTION (default 0/off) — the MCP SDK's localhost Host-header guard. It returns 421 behind a reverse proxy, so it's off by default (the service is TLS-terminated + Bearer-authed). Set 1 to enforce, with MCP_ALLOWED_HOSTS.
  • MCP_ALLOWED_HOSTS — comma-separated allowlist used only when the guard is on (default: the two fleetmcp.* domains + localhost).

1b. Manual host deploy (fallback): check this repo out on the host and run deploy.sh — it builds the image, derives the read-only DATABASE_URL from the running stack, and runs a standalone Traefik bridge. See the script header.

Add to Claude (for analysts)

claude mcp add --transport http fireside-analytics https://fleetmcp.rahamafresh.com \
  --header "Authorization: Bearer <your-token>"
claude mcp list      # → "fireside-analytics: connected"

Claude Desktop / claude.ai: add a custom connector with the same URL and Authorization: Bearer <your-token> header.

Local dev

uv sync
DATABASE_URL=postgresql://... MCP_AUTH_TOKENS=me:dev uv run uvicorn analytics_mcp:app --port 8892

Full reference, security notes, and verification checklist: docs/ANALYTICS_MCP.md.