feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
-- analytics_ro_role.sql — dedicated read-only LOGIN role for the analytics MCP server.
|
|
|
|
|
--
|
|
|
|
|
-- Sibling of dashboard_ro_role.sql, but for the decision & analytics team's MCP
|
|
|
|
|
-- server (analytics_mcp_rev.py) rather than the dashboard bridge. A separate role
|
|
|
|
|
-- keeps the two access paths independently revocable and lets us apply tighter,
|
|
|
|
|
-- human-ad-hoc-query guards (statement_timeout, idle-txn timeout) without touching
|
|
|
|
|
-- the dashboard bridge's credential.
|
|
|
|
|
--
|
|
|
|
|
-- Run as the postgres SUPERUSER (CREATE ROLE), NOT via run_migrations.py (which
|
|
|
|
|
-- connects as the app role and may lack CREATEROLE). Apply with
|
|
|
|
|
-- scripts/bootstrap_analytics_ro.sh, which supplies the password as the psql
|
|
|
|
|
-- variable :ro_pw from a host-only 0600 file — so no secret lives in this repo.
|
|
|
|
|
--
|
|
|
|
|
-- It grants exactly the read surface the MCP server needs:
|
|
|
|
|
-- * SELECT on reporting.* and tracksolid.* (tables + views)
|
|
|
|
|
-- * SELECT on the reporting.v_trips MATERIALIZED VIEW — matviews are NOT
|
|
|
|
|
-- covered by GRANT ... ON ALL TABLES, so it must be named explicitly
|
|
|
|
|
-- * EXECUTE on the reporting.fn_* functions (so analysts can SELECT reporting.fn_...)
|
|
|
|
|
-- * DEFAULT PRIVILEGES so future objects created by the migration role are
|
|
|
|
|
-- auto-readable (no re-grant when we add views)
|
|
|
|
|
-- Read-only: no INSERT/UPDATE/DELETE and not the matview owner, so analytics_ro
|
|
|
|
|
-- can never write or REFRESH. Idempotent -> safe to re-apply (also rotates pw).
|
|
|
|
|
|
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
|
|
|
|
|
|
DO $role$
|
|
|
|
|
BEGIN
|
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'analytics_ro') THEN
|
|
|
|
|
CREATE ROLE analytics_ro LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE;
|
|
|
|
|
END IF;
|
|
|
|
|
END $role$;
|
|
|
|
|
|
|
|
|
|
ALTER ROLE analytics_ro WITH LOGIN PASSWORD :'ro_pw';
|
|
|
|
|
|
|
|
|
|
GRANT CONNECT ON DATABASE tracksolid_db TO analytics_ro;
|
2026-06-17 08:04:41 +00:00
|
|
|
GRANT USAGE ON SCHEMA reporting, tracksolid, tickets, fuel TO analytics_ro;
|
feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
|
|
|
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO analytics_ro; -- tables + views
|
|
|
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid TO analytics_ro; -- tables + views
|
2026-06-17 08:04:41 +00:00
|
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA tickets TO analytics_ro; -- INC/CRQ tickets: tables + views
|
|
|
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA fuel TO analytics_ro; -- fuel: tables + views
|
feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
GRANT SELECT ON reporting.v_trips TO analytics_ro; -- MATERIALIZED VIEW (not in ALL TABLES)
|
|
|
|
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA reporting TO analytics_ro;
|
2026-06-17 08:04:41 +00:00
|
|
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA tickets TO analytics_ro;
|
|
|
|
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA fuel TO analytics_ro;
|
feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
|
2026-06-17 08:04:41 +00:00
|
|
|
-- "dynamic": future objects are auto-granted. reporting/tracksolid are created by the
|
|
|
|
|
-- migration role (tracksolid_owner); tickets/fuel are owned by postgres, so their
|
|
|
|
|
-- default-privilege grants must be keyed to postgres or new objects won't be readable.
|
|
|
|
|
-- NOTE: matviews are still never covered — a new matview needs its own explicit GRANT.
|
feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA reporting GRANT SELECT ON TABLES TO analytics_ro;
|
|
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA tracksolid GRANT SELECT ON TABLES TO analytics_ro;
|
|
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA reporting GRANT EXECUTE ON FUNCTIONS TO analytics_ro;
|
2026-06-17 08:04:41 +00:00
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA tickets GRANT SELECT ON TABLES TO analytics_ro;
|
|
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA fuel GRANT SELECT ON TABLES TO analytics_ro;
|
|
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA tickets GRANT EXECUTE ON FUNCTIONS TO analytics_ro;
|
|
|
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA fuel GRANT EXECUTE ON FUNCTIONS TO analytics_ro;
|
feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.
- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
list_tables, describe_table, list_functions, sample_table. Per-analyst
Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
add-to-Claude, verification, security notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:43:24 +00:00
|
|
|
|
|
|
|
|
-- Extra hardening over dashboard_ro: this role serves ad-hoc HUMAN queries via the
|
|
|
|
|
-- MCP server, so pin read-only at the role level and cap runaway work. These are
|
|
|
|
|
-- belt-and-braces alongside the read-only txn the server itself uses.
|
|
|
|
|
ALTER ROLE analytics_ro SET default_transaction_read_only = on;
|
|
|
|
|
ALTER ROLE analytics_ro SET statement_timeout = '30s';
|
|
|
|
|
ALTER ROLE analytics_ro SET idle_in_transaction_session_timeout = '60s';
|