Admin Setup Guide ← All Guides

Admin Setup Guide

For the admin — GCP configuration, IAM, onboarding marketers, ongoing ops, and data source ingestion.

Looking for the lifecycle scripts? Jump to Onboard a Marketer for add-marketer.sh (project + dataset-scoped IAM grants in one command) or Offboard a Marketer for remove-marketer.sh (revokes every binding the marketer holds across given projects).
Setting up data ingestion? See the dedicated Data Source Setup guide — decision matrix and per-source playbooks for GA4, Search Console, Google/Meta/Bing/LinkedIn ads, Reddit, LLM monitoring, and 7 more sources.

Executive Summary

This guide deploys a working pipeline that lets AimClear marketers ask plain-English questions about client BigQuery data through Claude. Rather than building custom infrastructure, we use Google's official BigQuery MCP toolbox bridged into Claude Code (or Claude Desktop) on each marketer's machine.

What this enables today:

What's already proven working (pilot project: marty-search-console-ga4-api):

Time to onboard a new marketer: ~15 minutes once GCP-side IAM is in place.
Time to onboard a new client project: ~30 minutes one-time, plus per-marketer above.

Architecture

┌────────────┐       ┌─────────────────┐       ┌──────────────────┐
│  Marketer  │──────▶│  Claude Code /  │──────▶│  MCP Toolbox     │
│  (browser  │       │  Claude Desktop │ stdio │  (local binary)  │
│  or app)   │       │                 │       │                  │
└────────────┘       └─────────────────┘       └────────┬─────────┘
                                                        │
                                              Google Application
                                              Default Credentials
                                                  (OAuth2)
                                                        │
                                                        ▼
                                              ┌──────────────────┐
                                              │  Google BigQuery │
                                              │  (client data)   │
                                              └──────────────────┘

                Security gates (in order, all enforced server-side):
                ┌─────────────────────────────────────────────────┐
                │  1. IAM allow grants (read-only roles only)     │
                │  2. IAM deny policy: tool.isReadOnly=false      │
                │  3. BigQuery time travel (7-day undo)           │
                │  4. Audit log of every query (goog-mcp-server)  │
                └─────────────────────────────────────────────────┘

Key architectural decisions:

Prerequisites

Per client (one-time)

Per marketer

Sysadmin (Alyssa)

One-Time GCP Project Setup

Run these once per client project. All commands are for Alyssa on her admin machine.

5.1 Enable required APIs

PROJECT=marty-search-console-ga4-api   # change per project

gcloud services enable bigquery.googleapis.com --project=$PROJECT
gcloud beta services mcp enable bigquery.googleapis.com --project=$PROJECT

5.2 Create the marketer Google Group (for deny policy only)

If you don't already have one, create a Google Group marketers@aimclear.com in Google Workspace Admin → Groups → Create.

This group is used exclusively as the target for the IAM Deny Policy in §5.4 — it blocks write-capable MCP tools for everyone in it. It is NOT used to grant BigQuery read access. Read access is granted per-individual per-project using the add-marketer.sh script (see Onboard a Marketer below).

5.3 Grant per-marketer read-only BigQuery + MCP roles

Use the add-marketer.sh script. It grants the three required roles individually per marketer per project:

# Full project access (dataViewer on all datasets):
./add-marketer.sh amber@aimclear.com client-amsoil-dealer-pages tonies-dynamic-landing

# Dataset-scoped access (dataViewer on specific datasets only):
./add-marketer.sh amber@aimclear.com tonies-dynamic-landing:ga4_data,search_console

Roles granted per project:

Do not grant dataEditor, dataOwner, or Editor/Owner at the project level.

5.4 Apply the deny policy (defense-in-depth)

This blocks write-capable MCP tools (execute_sql running anything other than SELECT) even if a marketer somehow ends up with elevated permissions for unrelated reasons.

Save as mcp-deny-readwrite.json:

{
  "rules": [
    {
      "denyRule": {
        "deniedPrincipals": [
          "principalSet://goog/group/marketers@aimclear.com"
        ],
        "deniedPermissions": [
          "mcp.googleapis.com/tools.call"
        ],
        "denialCondition": {
          "title": "Deny write-capable MCP tools for marketers",
          "expression": "api.getAttribute('mcp.googleapis.com/tool.isReadOnly', false) == false"
        }
      }
    }
  ]
}

Apply:

gcloud iam policies create mcp-deny-readwrite \
  --attachment-point=cloudresourcemanager.googleapis.com/projects/$PROJECT \
  --kind=denypolicies \
  --policy-file=mcp-deny-readwrite.json
The mcp-deny-readwrite.json file is a working artifact — only needed during apply. Once GCP accepts it, the policy lives server-side. Recommended: keep the file in a private AimClear-internal infra repo so it's version-controlled.

5.5 Verify the policy is active

gcloud iam policies list-deny-policies \
  --attachment-point=cloudresourcemanager.googleapis.com/projects/$PROJECT

You should see mcp-deny-readwrite listed.

Onboard a Marketer

Once GCP is set up, onboarding a new marketer is a single command. The add-marketer.sh script adds them to the marketers@aimclear.com group (covers the deny policy) and grants the read-only BigQuery + MCP roles on every project you list. As of v2 it also supports dataset-scoped access — you can grant a marketer read access to specific datasets within a project rather than the whole project.

Precondition: The IAM deny policy from §5.4 must already exist at the org or folder level and target the marketers@aimclear.com group. The script checks for this before making changes — if no deny policy is found, it warns and prompts you to confirm before proceeding (group membership without the deny policy has no protective effect).

add-marketer.sh

Run this on your admin machine with an account that has roles/iam.securityAdmin on each target project plus Workspace Admin access for group membership. The bq CLI (part of the Google Cloud SDK) must be on your PATH alongside gcloud — it handles the dataset-level binding when scoped access is requested.

⬇ Download add-marketer.sh(~6 KB)

Usage:

chmod +x add-marketer.sh
./add-marketer.sh <marketer-email> <project>[:<dataset1>,<dataset2>,...] [...]

Example 1 — full project access (all datasets in both projects):

./add-marketer.sh amber@aimclear.com tonies-dynamic-landing client-amsoil-dealer-pages

Example 2 — dataset-scoped on one, full on another (only ga4_data + search_console on Tonies, all datasets on Amsoil):

./add-marketer.sh amber@aimclear.com tonies-dynamic-landing:ga4_data,search_console client-amsoil-dealer-pages

Example 3 — dataset-scoped on both:

./add-marketer.sh amber@aimclear.com tonies-dynamic-landing:ga4_data client-amsoil-dealer-pages:amsoil_paid

How role scoping works

Three IAM roles are involved, each granted at the appropriate level:

When a marketer has dataset-scoped dataViewer, they can read those datasets only — attempting to list or query other datasets in the same project will return a permission denied error from BigQuery itself, regardless of what the MCP tries.

What the script does, step by step

  1. Deny policy sanity check: queries the org for existing IAM deny policies and warns if none are found (group membership alone provides no protection without one).
  2. Adds marketer-email to the marketers@aimclear.com Google Group via gcloud identity groups memberships add — this is what makes the deny policy from §5.4 apply to them.
  3. For each project argument, splits on the colon: project ID on the left, optional comma-separated dataset list on the right.
  4. Always grants roles/bigquery.jobUser and roles/mcp.toolUser at the project level via gcloud projects add-iam-policy-binding.
  5. If no datasets were listed: grants roles/bigquery.dataViewer at the project level (full project access).
  6. If datasets were listed: grants roles/bigquery.dataViewer at each individual dataset via bq add-iam-policy-binding "<project>:<dataset>".
  7. Prints a confirmation summary listing every project + dataset scope the marketer can now query.

The marketer then runs the appropriate setup script for their OS — see the Marketer MCP Setup Guide.

Offboard a Marketer

When a marketer leaves AimClear or no longer needs pipeline access, remove-marketer.sh revokes every IAM binding they hold across the projects you list (project-level and dataset-level) and removes them from the marketers@aimclear.com deny-policy group. Safe to run idempotently — it skips bindings that don't exist.

remove-marketer.sh

Run this on your admin machine with the same prerequisites as add-marketer.sh: roles/iam.securityAdmin on each target project, Google Workspace Admin access for group membership, and bq CLI on PATH (used to scan datasets in the project for dataViewer bindings to remove).

⬇ Download remove-marketer.sh(~4 KB)

Usage:

chmod +x remove-marketer.sh
./remove-marketer.sh <marketer-email> <project-id> [project-id ...]

Example:

./remove-marketer.sh amber@aimclear.com tonies-dynamic-landing client-amsoil-dealer-pages

What the script does, per project

After all projects are processed, removes the marketer from the marketers@aimclear.com Google Group via gcloud identity groups memberships delete. The deny-policy coverage drops with that membership change.

Verification: Ask the now-offboarded marketer to re-run a BigQuery query through Claude — it should return a 403 Permission Denied. Their local ADC token is harmless without the underlying IAM bindings.

Ops Responsibility Map

Audience: Alyssa (and anyone covering for her).
Context: I'm wearing every hat on this — GCP, IAM, MDM, BigQuery, Workspace, secrets — but each ongoing task lives in a different console or tool. This is a quick-reference so I open the right one without thinking about it.

The Map

Task Tool / Console Frequency
Add a new marketer / grant client access add-marketer.sh from Cloud Shell — grants individual IAM bindings per project; also adds to marketers@aimclear.com for deny policy coverage Per hire / per assignment
Revoke a marketer's access (offboarding) remove-marketer.sh from Cloud Shell — removes all project and dataset-level bindings; removes from deny group Per departure
Add a new client project to a marketer's access add-marketer.sh <email> <new-project> from Cloud Shell Per assignment
Add a new client project to AimClear's portfolio GCP Console — enable BigQuery API, apply deny policy, then add-marketer.sh per marketer Per new client
Push toolbox.yaml to all marketer laptops MDM (Intune / Jamf / whatever we run) Rare — only on capability changes
Push toolbox binary updates MDM ~Quarterly
Rotate genuine secrets (OAuth client secrets, service account JSON keys, ad-channel API tokens) LastPass CLI (AimClear standard, with 2FA) Per security policy
Investigate "why can't [marketer] see [client]?" GCP IAM — check project and dataset-level bindings for that marketer As needed
Audit who ran what query GCP Logging — filter resource.type="bigquery_resource" AND protoPayload.metadata.jobChange.job.jobConfig.queryConfig.labels.goog-mcp-server="true" As needed
Set or adjust per-project query cost caps GCP Console → IAM & Admin → Quotas → BigQuery Per project, then rarely

For each task there's exactly one console to open. That's the point of this map — same person, but different mental mode per task.

Directory decision: resolved — no table needed

IAM is the sole source of truth. When a marketer asks about a client, Claude queries the Google Cloud Resource Manager API using the marketer's credentials — this returns exactly the projects that marketer's Google account has IAM bindings on. No BigQuery directory table to maintain, no drift between IAM and a separate list.

Adding a new client to a marketer's view = running add-marketer.sh with the marketer's email and the new project. The change is visible to Claude immediately.

What's NOT in this map (and why)

These are real ongoing concerns, but not Alyssa-managed:

One assumption to flag

The whole architecture is built on the assumption that multi-channel data (Google Ads, Meta, LinkedIn, etc.) streams into BigQuery via central ingestion pipelines — not that marketers individually connect to ad platforms via MCP.

Practically this means:

If that assumption ever breaks (e.g., we decide marketers should be able to push changes back to ad platforms via Claude), the entire credential and security model reopens. Worth a deliberate conversation before going there.

Security Model

8.1 What CAN a marketer do?

8.2 What CANNOT a marketer do?

8.3 Why we're confident

Three independent layers, all of which would have to fail at once for harm to occur:

  1. IAM allow grants are read-only roles only (bigquery.dataViewer, bigquery.jobUser, mcp.toolUser). No write permission means no write capability.
  2. IAM deny policy explicitly blocks mcp.tools.call for write-capable tools using tool.isReadOnly == false. This is a server-side enforcement that overrides any allow grant — even if someone accidentally granted Owner later, the deny still wins.
  3. BigQuery time travel retains 7 days of table state. If something somehow slipped through both layers above, recovery is one query.

8.4 What happens if a marketer leaves AimClear?

Two steps:

  1. Remove per-project IAM bindings — run remove-marketer.sh (see Offboard a Marketer above) which executes gcloud projects remove-iam-policy-binding for bigquery.dataViewer, bigquery.jobUser, and mcp.toolUser on each project they had access to.
  2. Remove from marketers@aimclear.com group — keeps the deny policy membership list clean.

Their local ADC credentials file becomes useless once the IAM bindings are gone. No API keys or service account secrets to chase down.

FAQ — Sysadmin

Q: How is auth handled? Is there an API key I have to rotate?

No API keys for BigQuery access. Each marketer authenticates via their own Google account using gcloud auth application-default login. Tokens are stored locally and refresh automatically. Revocation is via Google Workspace or by removing them from the marketers group.

Q: How do I revoke a marketer's access immediately?

Run remove-marketer.sh <email> <project> [project ...] to strip every IAM binding (project + dataset level), then remove them from the marketers@aimclear.com group. IAM updates propagate within ~1 minute. Their next BigQuery query will fail with 403 Permission Denied. Their local ADC token is useless without the IAM bindings behind it.

Q: How do I audit who ran what query?

GCP Console → Logging → Logs Explorer. Filter by:

resource.type="bigquery_resource"
protoPayload.metadata.jobChange.job.jobConfig.queryConfig.labels.goog-mcp-server="true"

Every MCP-driven query is auto-tagged with that label. The protoPayload.authenticationInfo.principalEmail field shows the actual user.

Q: What if the toolbox binary updates?

Google publishes new versions to https://storage.googleapis.com/genai-toolbox/vX.Y.Z/. To upgrade a marketer:

  1. Replace their toolbox binary with a new version
  2. Run ./toolbox --version to confirm
  3. No Claude config changes needed

We should pin a known-good version organization-wide to avoid drift between marketers. Current pinned: v0.32.0.

Q: What if BigQuery exports change schema?

The MCP introspects the schema fresh on every conversation, so schema changes are picked up automatically. No code on our side needs updating. Worst case: Claude returns "I don't see a column called X" and the marketer tries a different question.

Q: What happens if a marketer accidentally racks up a huge BigQuery bill?

Two cost controls available:

  1. Project-level quota: GCP Console → IAM & Admin → Quotas → BigQuery query usage per day. Set a hard cap.
  2. Per-query bytes-billed limit: can be set as a default project property; queries that estimate exceeding it are rejected before running.

Recommend: set per-project daily query quota at ~10x typical use as a safety net.

Q: Can we centrally host the MCP toolbox instead of installing per laptop?

Yes, eventually. Google's hosted BigQuery MCP server (https://bigquery.googleapis.com/mcp) does exactly this — but requires an OAuth client setup per Claude install, which is more friction for marketer onboarding than ADC. Worth migrating to once we have 5+ marketers and the per-laptop install starts feeling tedious.

Q: Does this work behind AimClear's VPN / firewall?

Yes, as long as marketers can reach *.googleapis.com and *.google.com (they already can — they use Workspace). The toolbox makes outbound HTTPS calls to BigQuery; nothing inbound.

Troubleshooting

bigquery-X — ✗ Failed to connect after claude mcp list

Check, in order:

  1. Toolbox binary is present and executable: ~/bin/toolbox --version should print a version
  2. ADC is set up: gcloud auth application-default print-access-token should print a token
  3. The configured BIGQUERY_PROJECT exists and you have access: gcloud projects describe PROJECT_ID
  4. MCP service is enabled on the project: check with Alyssa

Error 403: BigQuery: Permission denied while getting Drive credentials

The query touches a Sheets-backed external table but ADC lacks Drive scope. Re-auth with:

gcloud auth application-default login \
  --scopes=https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/bigquery

Error 403: SERVICE_DISABLED: Data Analytics API with Gemini has not been used

Only the ask_data_insights tool needs this. Either:

Error: Permission 'mcp.tools.call' denied on tool by deny policy

Working as intended — the deny policy from §5.4 caught a write attempt. Confirm with the marketer that they were trying to do something read-only; if so, the query may be misclassified. If not, the system is doing its job.

Claude Desktop config not picked up

Restart Claude Desktop fully (quit + reopen), don't just close the window. On macOS: Cmd+Q. On Windows: right-click the tray icon → Quit, then reopen.

Claude Code MCP showing as connected but tools not appearing in conversation

Start a fresh Claude Code session — MCP tools are registered at session start. /reload-plugins does NOT reload MCP servers (it's for plugins/skills/agents/hooks).

Quick Reference

PROJECT_ID:       marty-search-console-ga4-api    (pilot)
TOOLBOX VERSION:  v0.32.0
MARKETER ROLES:   bigquery.dataViewer, bigquery.jobUser, mcp.toolUser
DENY POLICY:      mcp-deny-readwrite (blocks tool.isReadOnly=false)
GROUP:            marketers@aimclear.com

COMMANDS CHEAT SHEET (sysadmin)
  Enable APIs:       gcloud services enable bigquery.googleapis.com --project=$PROJECT
                     gcloud beta services mcp enable bigquery.googleapis.com --project=$PROJECT
  Grant marketer:    gcloud projects add-iam-policy-binding $PROJECT \
                       --member="user:NAME@aimclear.com" --role="roles/bigquery.dataViewer"
                     (repeat for jobUser, mcp.toolUser)
  Apply deny policy: gcloud iam policies create mcp-deny-readwrite \
                       --attachment-point=cloudresourcemanager.googleapis.com/projects/$PROJECT \
                       --kind=denypolicies --policy-file=mcp-deny-readwrite.json
  Audit log filter:  resource.type="bigquery_resource" AND
                     protoPayload.metadata.jobChange.job.jobConfig.queryConfig.labels.goog-mcp-server="true"

COMMANDS CHEAT SHEET (marketer, one-time setup)
  Authenticate:      gcloud auth application-default login
  Add to Claude:     claude mcp add --scope user bigquery ~/bin/toolbox \
                       --env BIGQUERY_PROJECT=marty-search-console-ga4-api \
                       -- --prebuilt bigquery --stdio
  Verify:            claude mcp list