GTM engineeringTutorial
Connecting Search Console to an AI agent when your org blocks service-account keys
The three errors you hit wiring Search Console to an MCP server, and the ADC path around them. With the exact API responses.
What we ranThe three exact API error payloads (FAILED_PRECONDITION on key creation, SERVICE_DISABLED without a quota project, 403 on sitemap submit) captured while wiring this up on 2026-07-20, plus the working ADC scope string.
Most guides for connecting Google Search Console to an MCP server tell you to create a
service account, download a JSON key, and point GOOGLE_APPLICATION_CREDENTIALS at it.
On a Google Cloud org with any security posture, step two fails. Here is what actually
comes back, and the path that works without weakening your org policy.
The key-creation wall
Creating the service account succeeds. Creating its key does not:
gcloud iam service-accounts keys create ./sa-key.json \
[email protected]
ERROR: (gcloud.iam.service-accounts.keys.create) FAILED_PRECONDITION:
Key creation is not allowed on this service account.
- '@type': type.googleapis.com/google.rpc.PreconditionFailure
violations:
- description: Key creation is not allowed on this service account.
type: constraints/iam.disableServiceAccountKeyCreation
constraints/iam.disableServiceAccountKeyCreation is a common org-level guardrail, and it
exists for a good reason: a downloaded key is a long-lived credential that lands in
someone's home directory and stays there. You can override it per-project, but that
means enabling the Org Policy API and turning off a security control for a read-only
reporting integration. Bad trade.
Use application default credentials instead
Search Console's API cares about who is authorised on the property, and a human account
already is. google-auth-library — which most MCP servers for this use — accepts an
authorized_user credential in the same keyFile slot as a service-account key, so
nothing on the server side has to change:
gcloud auth application-default login \
--scopes=https://www.googleapis.com/auth/webmasters.readonly,\
https://www.googleapis.com/auth/cloud-platform,openid,\
https://www.googleapis.com/auth/userinfo.email
The scopes matter. For a user credential the scopes granted at consent time are the ones
you get — passing scopes to GoogleAuth later is silently ignored, so if you omit
webmasters.readonly here you will get an opaque permission error at call time with
nothing pointing back at this command.
Point the server at the resulting file:
GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/application_default_credentials.json
The quota-project error
Authentication now works and the API still refuses you:
{
"error": {
"code": 403,
"message": "Your application is authenticating by using local Application Default Credentials. The searchconsole.googleapis.com API requires a quota project, which is not set by default.",
"status": "PERMISSION_DENIED",
"details": [{ "reason": "SERVICE_DISABLED" }]
}
}
User credentials have no project to bill quota against, so the API wants one named
explicitly. gcloud auth application-default login writes quota_project_id into the
credentials file, and google-auth-library reads it and sends the header for you — which
is why the MCP server works while your curl reproduction does not. For raw calls you
have to add it yourself:
TOKEN=$(gcloud auth application-default print-access-token)
curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "x-goog-user-project: my-project" \
https://www.googleapis.com/webmasters/v3/sites
{
"siteEntry": [
{ "siteUrl": "sc-domain:example.com", "permissionLevel": "siteOwner" }
]
}
Also enable the API on that project first — gcloud services enable searchconsole.googleapis.com — or the same call fails with SERVICE_DISABLED and a
message about the quota project, which sends you back down the wrong path.
What read-only actually costs you
webmasters.readonly covers everything an agent needs to analyse: performance data,
URL inspection, listing sitemaps. It does not cover submitting one:
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "x-goog-user-project: my-project" \
"https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Aexample.com/sitemaps/https%3A%2F%2Fexample.com%2Fsitemap.xml"
# 403
Swapping to the read-write webmasters scope fixes it. We deliberately did not. An agent
that reads your search data and an agent that can mutate your Search Console configuration
are different risk profiles, and sitemap submission is a once-per-site action — not worth
handing over write access to automate. If you are wiring an agent that runs unattended,
read-only is the right default and the 403 is the feature.
Caveats worth knowing before you copy this
The credential is tied to a human account, which has two consequences. It lives on one machine, so a cloud job cannot use it — for unattended remote runs you are back to needing a service-account key, and therefore back to the org-policy conversation. And it inherits that person's access: if they leave or lose the property, the integration dies with no warning beyond a sudden permission error.
For a laptop-side agent reading its own site's search data, that is a fair trade. For anything shared or scheduled in the cloud, plan on the key.
Verified against the Search Console API on 2026-07-20. Error payloads are real responses, lightly redacted for project and domain names.