Admin Setup Guide
For the admin — GCP configuration, IAM, onboarding marketers, ongoing ops, and data source ingestion.
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).
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:
- Marketers ask questions like "Which channel had the best ROAS last week?" directly inside Claude
- Claude inspects the BigQuery schema, writes the SQL itself, runs it, and returns a marketer-friendly summary
- All access is read-only at multiple layers, so marketers cannot accidentally damage client data
What's already proven working (pilot project: marty-search-console-ga4-api):
- Authentication via Google account → BigQuery (no API keys, no service account JSONs handed to marketers)
- Schema introspection on real GA4 connector tables (
p_ga4_*) - Live SQL execution with a sample marketer-style question, returning the right answer
- Read-only Google Developer Knowledge MCP added so Claude can self-reference Google's official docs while building/debugging
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:
- No custom MCP server built. We use Google's
genai-toolbox --prebuilt bigquerybinary, which is officially supported, free, and updated by Google. - Local toolbox per marketer rather than centrally hosted. This keeps each marketer's Google identity in their own MCP transactions, so audit logs are accurate per-user.
- No API keys for BigQuery access. Authentication is the marketer's own Google account via OAuth — meaning offboarding is just removing IAM grants in GCP, with no key rotation needed.
Prerequisites
Per client (one-time)
- A GCP project containing the client's BigQuery datasets (existing AimClear projects are fine)
- BigQuery API enabled on that project (
bigquery.googleapis.com) - Billing attached to the project (BigQuery is pay-per-query)
Per marketer
- A Google Workspace account (e.g.,
firstname@aimclear.com) - Either Claude Code or Claude Desktop installed
- Admin rights on their own laptop (to install gcloud + toolbox)
Sysadmin (Alyssa)
roles/serviceusage.serviceUsageAdminandroles/iam.securityAdminon the target GCP project (or higher)
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.
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:
bigquery.dataViewer— read data (at project or dataset level depending on args)bigquery.jobUser— run query jobs (always project-level)mcp.toolUser— use the MCP toolbox (always project-level)
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
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.
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.
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:
roles/bigquery.jobUser— always at the project level (required to run any query in that project; can't be scoped lower)roles/mcp.toolUser— always at the project level (required to invoke the MCP at all)roles/bigquery.dataViewer— granted at the project level when no datasets are specified, or at each dataset level when a colon-separated list is provided
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
- 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).
- Adds
marketer-emailto themarketers@aimclear.comGoogle Group viagcloud identity groups memberships add— this is what makes the deny policy from §5.4 apply to them. - For each project argument, splits on the colon: project ID on the left, optional comma-separated dataset list on the right.
- Always grants
roles/bigquery.jobUserandroles/mcp.toolUserat the project level viagcloud projects add-iam-policy-binding. - If no datasets were listed: grants
roles/bigquery.dataViewerat the project level (full project access). - If datasets were listed: grants
roles/bigquery.dataViewerat each individual dataset viabq add-iam-policy-binding "<project>:<dataset>". - 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).
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
- Removes
roles/bigquery.jobUserat the project level (if present) - Removes
roles/mcp.toolUserat the project level (if present) - Removes
roles/bigquery.dataViewerat the project level (if present) - Scans every dataset in the project via
bq lsand removesroles/bigquery.dataViewerat the dataset level wherever the marketer is bound
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.
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:
- Per-marketer Google authentication. Handled by
gcloud auth application-default loginon the marketer's own machine. Tokens refresh automatically. No admin action required after initial onboarding. - Claude Desktop / Claude Code config on the marketer's laptop. The marketer owns this; if they break it, they fix it (with the deployment guide).
- Genuine secrets storage. AimClear standard is LastPass with CLI + 2FA. Not GCP Secret Manager. Don't reintroduce a second secrets store — having two is worse than one.
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:
- Marketers' MCP stack stays simple: one BigQuery MCP, that's it
- All the channel-credential complexity (API keys, OAuth, rate limits) lives in our ingestion layer
- We're not handing marketers a tangle of per-channel credentials to manage
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?
- Read any data in BigQuery datasets they've been granted access to
- Run any SELECT query (subject to BigQuery cost limits)
- See full schemas and metadata
- Trigger queries via natural language without needing to know SQL
8.2 What CANNOT a marketer do?
- Write, update, or delete any BigQuery data
- Create or drop tables, datasets, or projects
- Access projects they haven't been granted IAM roles on
- Reach data outside BigQuery (no Google Drive, Sheets, or other GCP services unless granted separately)
- Run queries on behalf of someone else's identity
8.3 Why we're confident
Three independent layers, all of which would have to fail at once for harm to occur:
- IAM allow grants are read-only roles only (
bigquery.dataViewer,bigquery.jobUser,mcp.toolUser). No write permission means no write capability. - IAM deny policy explicitly blocks
mcp.tools.callfor write-capable tools usingtool.isReadOnly == false. This is a server-side enforcement that overrides any allow grant — even if someone accidentally grantedOwnerlater, the deny still wins. - 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:
- Remove per-project IAM bindings — run
remove-marketer.sh(see Offboard a Marketer above) which executesgcloud projects remove-iam-policy-bindingforbigquery.dataViewer,bigquery.jobUser, andmcp.toolUseron each project they had access to. - Remove from
marketers@aimclear.comgroup — 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:
- Replace their
toolboxbinary with a new version - Run
./toolbox --versionto confirm - 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:
- Project-level quota: GCP Console → IAM & Admin → Quotas → BigQuery query usage per day. Set a hard cap.
- 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:
- Toolbox binary is present and executable:
~/bin/toolbox --versionshould print a version - ADC is set up:
gcloud auth application-default print-access-tokenshould print a token - The configured
BIGQUERY_PROJECTexists and you have access:gcloud projects describe PROJECT_ID - 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:
- Enable:
gcloud services enable geminidataanalytics.googleapis.com --project=PROJECT_ID(Alyssa) - Or just don't use that specific tool —
execute_sqlcovers the same use cases
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