feat(access): expose tickets + fuel schemas to analytics_ro (read-only)

The analytics_ro role only had USAGE/SELECT on reporting + tracksolid, so
the tickets schema (INC/CRQ, 8 tables + 1 view + 7 fns) and fuel schema
were invisible to the MCP server — queries failed with permission denied.

- analytics_ro_role.sql: GRANT USAGE/SELECT/EXECUTE on tickets + fuel.
  Default privileges for these are keyed to postgres (their owner), not
  tracksolid_owner, so future objects auto-grant correctly.
- analytics_mcp.py: READABLE_SCHEMAS now includes tickets + fuel and is
  overridable via MCP_READABLE_SCHEMAS, so the introspection helpers
  (list_tables/describe_table/sample_table) work for them too.
- deploy.sh: reuse existing analyst tokens from the running container when
  MCP_AUTH_TOKENS is unset, so a code-only redeploy needs no secret.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kiania 2026-06-17 11:37:25 +03:00
parent 0c4848c656
commit f83f67e73f
3 changed files with 31 additions and 5 deletions

View file

@ -64,7 +64,15 @@ log = _get_logger("server")
DATABASE_URL = os.environ["DATABASE_URL"] # analytics_ro DSN (set by deploy) DATABASE_URL = os.environ["DATABASE_URL"] # analytics_ro DSN (set by deploy)
MAX_ROWS_CEIL = int(os.getenv("MCP_MAX_ROWS", "10000")) MAX_ROWS_CEIL = int(os.getenv("MCP_MAX_ROWS", "10000"))
READABLE_SCHEMAS = ("reporting", "tracksolid") # Schemas the introspection helpers (list_tables/describe_table/sample_table) expose.
# Override with MCP_READABLE_SCHEMAS="reporting,tracksolid,tickets,fuel" — these must
# stay in sync with the GRANTs in scripts/analytics_ro_role.sql. The raw query() tool
# is bounded by the analytics_ro role's GRANTs, not by this list.
READABLE_SCHEMAS = tuple(
s.strip() for s in os.getenv(
"MCP_READABLE_SCHEMAS", "reporting,tracksolid,tickets,fuel"
).split(",") if s.strip()
)
# ── Read-only connection pool ──────────────────────────────────────────────── # ── Read-only connection pool ────────────────────────────────────────────────
# Force read-only + a statement timeout at the connection level (belt + braces; # Force read-only + a statement timeout at the connection level (belt + braces;

View file

@ -26,6 +26,15 @@ PORT=8892
HOST_DOMAIN="${HOST_DOMAIN:-fleetmcp.fivetitude.com}" # prod: fleetmcp.rahamafresh.com HOST_DOMAIN="${HOST_DOMAIN:-fleetmcp.fivetitude.com}" # prod: fleetmcp.rahamafresh.com
IMAGE="fleetanalytics-mcp:latest" IMAGE="fleetanalytics-mcp:latest"
ENV_FILE="$(pwd)/.deploy.env" ENV_FILE="$(pwd)/.deploy.env"
# Per-analyst Bearer tokens. For a CODE-ONLY redeploy you can omit MCP_AUTH_TOKENS:
# we reuse the tokens from the currently running container so existing analysts keep
# working and no secret has to be re-typed or printed. Only set MCP_AUTH_TOKENS when
# you are adding/rotating/revoking a token.
if [ -z "${MCP_AUTH_TOKENS:-}" ]; then
MCP_AUTH_TOKENS="$(docker inspect "$NAME" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | sed -n 's/^MCP_AUTH_TOKENS=//p' | head -1)"
[ -n "$MCP_AUTH_TOKENS" ] && echo "Reusing existing analyst tokens from running $NAME container."
fi
: "${MCP_AUTH_TOKENS:?set MCP_AUTH_TOKENS=name:token[,name:token...] before running (per-analyst Bearer tokens)}" : "${MCP_AUTH_TOKENS:?set MCP_AUTH_TOKENS=name:token[,name:token...] before running (per-analyst Bearer tokens)}"
# Resolve the network + DB DSN from the running webhook_receiver (it sits on the # Resolve the network + DB DSN from the running webhook_receiver (it sits on the

View file

@ -33,19 +33,28 @@ END $role$;
ALTER ROLE analytics_ro WITH LOGIN PASSWORD :'ro_pw'; ALTER ROLE analytics_ro WITH LOGIN PASSWORD :'ro_pw';
GRANT CONNECT ON DATABASE tracksolid_db TO analytics_ro; GRANT CONNECT ON DATABASE tracksolid_db TO analytics_ro;
GRANT USAGE ON SCHEMA reporting, tracksolid TO analytics_ro; GRANT USAGE ON SCHEMA reporting, tracksolid, tickets, fuel TO analytics_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO analytics_ro; -- tables + views 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 GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid TO analytics_ro; -- tables + views
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
GRANT SELECT ON reporting.v_trips TO analytics_ro; -- MATERIALIZED VIEW (not in ALL TABLES) 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; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA reporting TO analytics_ro;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA tickets TO analytics_ro;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA fuel TO analytics_ro;
-- "dynamic": future objects created by the migration role (tracksolid_owner) -- "dynamic": future objects are auto-granted. reporting/tracksolid are created by the
-- are auto-granted. NOTE: matviews are still never covered — a new matview needs -- migration role (tracksolid_owner); tickets/fuel are owned by postgres, so their
-- its own explicit GRANT SELECT (as above for v_trips). -- 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.
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 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 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; ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA reporting GRANT EXECUTE ON FUNCTIONS TO analytics_ro;
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;
-- Extra hardening over dashboard_ro: this role serves ad-hoc HUMAN queries via the -- 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 -- MCP server, so pin read-only at the role level and cap runaway work. These are