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:
parent
0c4848c656
commit
3fcff8e4f7
3 changed files with 31 additions and 5 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue