Skip to main content
Glama
Mrshahidali420

Google Search Console MCP Server

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
gsc_list_sitesA

List every Search Console property this account can see.

Costs one Search Console API call (sites.list) — no per-property quota is spent. Persists each property into the local store (upserted by property URL) so other tools can route a page URL to its property without another network round trip; an existing row's sitemaps are left untouched here, since this call does not fetch or change them.

Returns [{"property", "host", "permission"}, ...] sorted by property. On a missing, expired, or rejected token, returns {"ok": False, "error": "auth_required", "fix": ...} instead of raising; if no OAuth client is configured at all, returns {"ok": False, "error": "not_configured", "fix": ...} instead — in either case the caller can surface fix directly rather than parsing an exception. A Search Console error returns {"ok": False, "error": "api_error", "status": <http status>, "fix": ...}, and anything else {"ok": False, "error": "unexpected", "detail": <exception type>, "fix": ...}. An EMPTY LIST therefore means what it says — this account really has no properties — and never a call that was refused.

Does not fetch sitemaps, index status, or search analytics; see gsc_doctor, gsc_check_status, and gsc_performance for those. A property already known to the store keeps whatever sitemaps a prior gsc_submit_sitemaps() call recorded against it — this call never fetches or clears that list, so a routine refresh cannot erase it.

gsc_doctorA

Diagnose whether gsc-mcp is set up to talk to Search Console.

Runs seven checks in order — oauth_client, token, config, store, properties, browser, extension — and reports all of them even if one raises. A check that raises is recorded as ok: False with the exception's TYPE NAME only in detail; the message is never included, because it can carry a bearer token, a credentialed URL, a raw response body, or a filesystem path holding your account name. Every failing check carries a non-empty fix string with a concrete next step; this tool diagnoses, it does not repair anything itself.

The last two are the local setup for browser-driven submission, and they come last because they cost nothing and the first five establish whether anything works at all. browser names the profile to use; extension reports whether the bridge extension is REGISTERED in that profile — a green check does not mean its background worker is running, which needs a live connection and arrives with Milestone 3B. "Could not be checked" is reported as such, never as "not installed".

Costs at most one Search Console API call (sites.list, for the properties check); the browser and extension checks are local file reads and make none.

Returns {"ok": bool, "checks": [{"name", "ok", "detail", "fix"}, ...]}; ok is true only when every check passed.

gsc_check_statusA

Check whether each URL is indexed by Google Search, via the URL Inspection API.

READ-ONLY: this tool inspects current index status and submits NOTHING. It never requests indexing and never spends a Request-Indexing slot. No tool here does yet — requesting indexing is not built, so an assistant asked to get a URL indexed should say so rather than reaching for this one, which answers a different question and spends a different budget doing it. This tool DOES spend URL Inspection quota, a separate per-property budget of 2,000 calls a day and roughly 600 a minute (see gsc_quota) — one call per URL inspected.

concurrency defaults to the configured inspect_concurrency (config.load()["inspect_concurrency"]) when omitted.

Properties come from the local store, the same one gsc_list_sites populates. If the store has never been synced — e.g. this is the very first call this install has ever made — it is synced automatically first, so a first-ever gsc_check_status call does not fail with "no property matches" purely because nothing has been synced yet.

Returns {"rows": [...], "checked": int, "skipped_quota": [...], "quota": {...}}. "quota" is per property, and its daily_free_at_gate/minute_free_at_gate are the headroom measured BEFORE this call reserved its own budget — they are a record of what the gate saw, not current headroom, and are already stale by the size of this batch by the time you read them. gsc_quota's similarly-shaped daily_free/minute_free are the ones measured now; do not compare the two pairs or plan a second batch against these. binding_at_gate (here and on each "skipped_quota" entry) carries the suffix for the same reason: it names only the exhausted INSPECTION window as seen at the gate, where gsc_quota's binding covers the submission budget too and is read now. Each row is {"url", "status", "detail", "unverified"}. status is one of: indexed, crawled_not_indexed, discovered_not_indexed, unknown_to_google, redirect, noindex, duplicate, alternate_canonical, not_found, soft_404, blocked_robots, no_property, error. no_property means no Search Console property in this account covers that URL's host. unverified is True when a concurrent burst produced a suspect result (unknown_to_google or error) that a sequential re-check could not confirm before quota or time ran out — treat such a row as UNKNOWN, not as a confirmed "not indexed".

On a missing, expired, or rejected token, returns {"ok": False, "error": "auth_required", "fix": ...} instead of raising; if no OAuth client is configured at all, returns {"ok": False, "error": "not_configured", "fix": ...}.

gsc_quotaA

Report Request-Indexing and URL Inspection budget for every property the store currently knows about.

Local-only: reads the store and the config file, makes no Search Console API call, and needs no OAuth token — safe to call at any time, including before signing in. An empty store (nothing synced yet via gsc_list_sites or gsc_check_status) returns [].

One entry per property: {"property", "submission", "inspection", "binding"}.

THESE NUMBERS ARE AN ESTIMATE, NOT A MEASUREMENT, and the estimate is biased in one direction. The store counts only the slots THIS TOOL spent: a URL submitted by hand in Search Console, from a phone, or on another machine never reaches it. So used is a lower bound and free an upper bound — a property reported with headroom can still be refused by Google, and that is expected behaviour rather than a defect. Do not tell a user "you have N submissions left" without that caveat, and do not conclude anything about Google's own rules from these counts. counts and free_is_upper_bound restate this in the payload so it survives being read without the docstring.

"submission" is the Request-Indexing slot budget: {"free", "spendable_free", "used", "slots", "daily_reserve", "next_free_at", "counts", "free_is_upper_bound", "last_refusal_at"}. last_refusal_at is the last time Google actually said Quota Exceeded for this property (ISO-8601, or None) — the only figure in the block that came from Google rather than from local arithmetic. free is the RAW free-slot count and ignores daily_reserve. spendable_free is computed against the RESERVE-ADJUSTED ceiling — max(0, (slots - daily_reserve) - used) — NOT simply free minus daily_reserve: that arithmetic breaks at the clamp (slots=11, daily_reserve=2, used=10 gives free=1, but spendable_free is 0, not -1). ACT ON spendable_free, NOT free: daily_reserve exists to hold slots back from every tool, and a caller that submits up to free instead will be refused once spendable_free runs out. next_free_at is an ISO-8601 string, or None when a slot is free right now — it is already computed against the reserve-adjusted ceiling too, so it can report a wait even while free (the raw count) is nonzero.

"inspection" is the URL Inspection API budget: {"daily_free", "minute_free", "daily_limit", "minute_limit"} (2000/day, 600/minute, per property — the same quota gsc_check_status spends).

"binding" names whichever budget is exhausted for that property right now — "submission" (the Request-Indexing ceiling, reserve applied), "refused" (Google said Quota Exceeded recently and a short cooldown is running), "inspection_daily", or "inspection_minute" — or None when every budget has headroom. On "refused", spendable_free may be nonzero: the local estimate is being overruled by what Google actually answered, and next_free_at is when to ask again rather than a moment a slot is known to exist.

gsc_performanceA

Search Analytics performance: clicks, impressions, ctr, position.

Three shapes, chosen by what is passed:

  • No site -> one row per property the store knows about (scope: "portfolio", sites, totals aggregated across all of them).

  • site, no dim -> a single aggregate for that site (scope: "site", plus clicks/impressions/ctr/position).

  • site and dim -> per-dim rows for that site, sorted by clicks descending (scope: <dim>, rows, totals aggregated across rows). dim must be one of perf.VALID_DIMENSIONS ("query", "page", "country", "device", "date", "searchAppearance").

Date window: pass start_date AND end_date (both "YYYY-MM-DD") for an explicit range, or leave both unset and get the trailing days calendar days ending yesterday. start_date without end_date is refused outright -- {"ok": False, "note": "start_date needs end_date (both YYYY-MM-DD)"} -- rather than guessing an end.

IMPORTANT -- data_state defaults to "all", not Google's own API default of "final". Passing data_state="final" attaches a warning string explaining why: data_state='final' silently omits the most recent ~perf.FINAL_LAG_DAYS (3) day(s) of data -- Google has not finished processing them yet, and nothing in the response marks the gap. The Search Console web UI has no such restriction and shows those days immediately, so a 'final' query compared against what a human sees in the UI right now will look like a discrepancy or a missed traffic change when both are simply answering different questions. Prefer data_state='all' (the default here) unless you specifically need finalized-only rows; its trade-off is that the last day or two may still revise upward on a later query.

On a missing, expired, or rejected token, returns {"ok": False, "error": "auth_required", "fix": ...}; if no OAuth client is configured at all, {"ok": False, "error": "not_configured", "fix": ...}. Any other failure -- a bad dimension, an unroutable site, a Search Console API error -- comes back as {"ok": False, "start", "end", "note": str(exc)} rather than raising.

gsc_submit_sitemapsA

Submit one or more sitemaps to Search Console (PUT, idempotent -- safe to resubmit an already-known sitemap).

sitemaps is an optional list of sitemap URLs; each is routed to its covering property via the same host-matching gsc_check_status uses. Omit it to resubmit every sitemap already on record for every property (the same list gsc_list_sites carries forward on refresh).

REFUSES TO GUESS: when sitemaps is omitted and the store has no sitemap recorded for any property, this returns {"ok": False, "note": "No sitemaps known. Pass sitemaps=[...] explicitly."} rather than trying /sitemap.xml -- submitting a URL nobody named is an outward-facing action against the caller's Search Console property, and a wrong guess leaves a permanent failed submission in their sitemap list for no reason.

Returns a list of {"site", "sitemap", "http_status", "ok", "note"} (api.submit_sitemap's shape) -- one entry per sitemap attempted, plus one {"site": None, "sitemap", "http_status": None, "ok": False, "note"} entry for any URL that matched no known property. Every successful submission is recorded back onto its property's row (merged with, never replacing, whatever sitemaps were already there), so a later bare call resubmits it too.

On a missing, expired, or rejected token, returns {"ok": False, "error": "auth_required", "fix": ...} instead of raising, once real work is about to start (nothing has been submitted yet at that point); if no OAuth client is configured at all, {"ok": False, "error": "not_configured", "fix": ...}. Any other failure comes back as {"ok": False, "error": "unexpected", "detail": <exception type>, "fix": ...}.

gsc_detect_browsersA

List the Chromium browser profiles on this machine and recommend one.

Local-only: reads the browsers' own state files, makes no network call, spends no quota, and needs no token — safe to call before signing in. Answers "which browser profile should I drive?", nothing else; it opens no browser and changes no setting.

PRIVACY: no email address is returned, in either direction. Not the account signed in to a profile, not the account that authorised this server, and not a profile display name that is itself an address. No filesystem path is returned either — a profile path carries the operator's account name. What identifies a profile here is its browser and its profile directory.

Returns {"ok": True, "profiles": [...], "recommended": <one of them or None>, "reasons": [...]}. profiles is a FLAT list across every browser, ranked-flag included, not grouped by browser — the question is which single profile to use, so each entry carries its own brand context (browser, browser_key, extensions_url) and stands alone. Each profile is {"browser", "browser_key", "extensions_url", "profile", "display_name", "account_on_disk", "account_discoverable", "matches_authorised_account", "has_extension", "recommended"}.

extensions_url is where that browser's extensions page lives (chrome://extensions, brave://extensions, ...). Use the value given; do not build one from the browser key, since Chromium registers no chromium:// scheme and uses chrome://extensions.

has_extension is TRI-STATE: true means the pairing extension is installed in that profile, false means every preferences file was read and it was not among them, and null means the check could not be PERFORMED — an unreadable preferences file, or no unpacked extension directory to match against. Read null as "not detected", never as "not installed"; telling a user with a working install to reinstall it is the one wrong answer this flag exists to avoid.

account_on_disk says an account address was found in that profile's files — found, not validated: no token is checked and nobody's live session is inspected, and on Microsoft Edge the same file records Microsoft accounts, so treat it as a hint rather than a login check. account_discoverable is a fact about the BRAND: Brave, Vivaldi, Opera and plain Chromium record no Google account at all, so account_on_disk: false there means "not discoverable", NOT "nobody is signed in". matches_authorised_account is true, false, or NULL — null means the question could not be asked (nothing has authorised yet, or the brand records nothing), and must not be read as "no". reasons explains the recommendation in plain sentences.

A machine with no Chromium browser installed returns ok: true with an empty profiles list, recommended: null, and a note saying what to install; that is an ordinary state, not a failure. Only an unexpected fault returns {"ok": False, "error": "unexpected", "detail": <exception type>, "fix": ...}.

gsc_use_browserA

Choose which browser profile this server drives, overriding detection.

Local-only: writes one preference to this server's config file, makes no network call, spends no quota, needs no token, and opens no browser. Use it when gsc_detect_browsers recommends a profile that is not the one holding your Search Console account — the detector can rank the profiles it finds, but it cannot know which browser you work in.

browser is the browser_key from gsc_detect_browsers ("chrome", "brave", "edge", "vivaldi", "opera", "chromium") — the key, not the display label. profile is the profile field from the same entry (the profile DIRECTORY, e.g. "Default" or "Profile 3"); omit it to take that browser's default profile. clear=True removes the pin and returns to the detector's recommendation, and ignores the other two.

The pair is checked against the profiles that actually exist BEFORE it is saved. A pair that matches nothing is refused with {"ok": False, "error": "browser_not_found", ...} whose fix lists the pairs that would work, so a typo is answered immediately rather than becoming a browser that silently never opens.

Once pinned, the choice is absolute: gsc_setup, gsc_doctor and every tool that drives the browser use it, and the ranking is not consulted. If the pinned profile later disappears — browser uninstalled, profile deleted — nothing falls back to a different one. Every affected tool stops and says the pin is dangling, because driving the wrong profile would submit URLs from whichever account happens to be signed in there.

The pin survives restarts. Returns {"ok": True, "pinned": "<browser> / <profile>" or null, "note": ...}.

gsc_setupA

Set this server up, one step at a time. Call it, do what it says, call it again — repeat until ok is true.

IDEMPOTENT and SAFE TO CALL REPEATEDLY. It spends no Search Console indexing quota and makes at most one API call (verifying the stored sign-in still works). Every call re-reports the whole state from scratch, so there is no session to resume and no order to get wrong: if you have lost track of where setup got to, just call it again.

There are four steps, checked in order: oauth_client (credentials to sign in with), consent (the user approves Google's consent screen), browser (a Chromium browser with a profile exists), extension (the gsc-mcp bridge extension is loaded in the profile this server recommends). The FIRST unsatisfied step is returned as next and the call stops there — later steps are meaningless until it is done.

Returns {"ok": bool, "done": [step], "pending": [step], "next": {"step", "action", "url"?, "path"?} | None}. ok is true, and next is null, only when all four steps are satisfied. next.action is a plain-English instruction to relay to the user. next.url, when present, is the Google consent URL to open. next.path, when present, is the folder to choose in the browser's "Load unpacked" dialog.

open_browser (default true) opens the consent URL in the user's default browser when a NEW consent is started. A repeat call while one is already pending returns the SAME url and opens nothing — the pending consent screen is the only one whose redirect will be accepted, so never assume a second call means a second link.

PRIVACY: no email address, no token field, no PKCE verifier and no authorization code is ever returned. The only filesystem path returned is next.path, which is this server's own extension directory.

Never raises. An unexpected fault comes back as next.step: "unexpected" with an action to retry.

gsc_request_indexingA

Submit up to five URLs to Google's Request Indexing, one at a time, through the browser extension in your own signed-in profile.

BLOCKING and slow by design: submissions are paced 130-180 seconds apart, so five URLs can take fifteen minutes. Use gsc_start_indexing_job for anything larger.

Quota is per property — roughly eleven slots per property on a rolling 24-hour window, and properties are independent. Call gsc_quota first to see what is spendable; act on spendable_free, not free.

Returns {"ok", "submitted", "skipped", "failed", "stopped_early", "stop_reason", "notes", "results"}, with one entry per URL in results. A run stops early on the first throttle, captcha, or signed-out session rather than burning the rest of the batch against a refusing server. A refusal is {"ok": False, "error", "detail", "fix"}.

gsc_start_indexing_jobA

Queue a background submission run over any number of URLs.

Returns immediately with {"ok", "job_id", "total", "note"}. Poll gsc_job_status for progress and gsc_stop_job to end it early. One submission job runs at a time: the bridge drives a single browser tab in your real profile, so a second job is refused rather than queued.

gsc_job_statusA

Progress and state for one submission job, or the most recent one.

States: pending, running, completed, stopped_user, stopped_throttled, failed. results holds one entry per URL attempted so far, and live says whether a worker is still on it in this process.

stop_reason says why a run ended early — "quota_exceeded", "no_quota", "stopped_by_user". It is the only trace of a run refused at the gate, which attempts nothing and so leaves results and error both empty.

gsc_stop_jobA

Ask a running submission job to stop.

It stops after the URL currently in flight, never mid-URL: a submission already sent has spent its quota slot and its ledger row must settle with the real outcome.

gsc_find_unindexedA

Which of a property's URLs are not in Google's index, and why.

source chooses where candidate URLs come from: "sitemap" fetches and parses the property's registered sitemaps fresh, "store" uses only URLs already seen, "both" (the default) unions them.

limit caps how many URLs are INSPECTED, not how many are returned — inspection spends a daily budget, so a cap that only trimmed the output would pay full price for an answer it threw away. Which URLs a capped run reaches follows the store's url ordering (alphabetical), not staleness: a capped run is a sample, not a worst-first sweep. The result reports candidates_total, inspected and limited so you can tell a truncated answer from a complete one.

limit is not the only thing that can cut a run short. Inspection quota is per property and roughly eleven slots a day, so on any site larger than that the run reaches the gate and stops: inspected is what was handed to the API, checked is what actually reached it, and skipped_quota lists the URLs accounting for the difference. Report both numbers rather than inspected alone — a run that answered for three of forty URLs is not a survey of the property, and the remaining URLs are answerable tomorrow at no extra cost.

Only URLs whose last inspection is older than inspection_ttl_days are re-inspected; the rest are reported from their stored status. A second call the same day therefore costs no budget and still answers in full. "fresh": true on a row means only that THIS run did not inspect it — usually because it was within the TTL, but also when limit cut the run short before reaching it. It is not a promise that the stored status is within the TTL.

Each unindexed row carries reason (one of ten codes), action, submitting_helps and needs_site_access. Act on submitting_helps before calling gsc_request_indexing: submitting a 404, a redirect, a noindex, or a page Google crawled and declined wastes an unrecoverable quota slot.

URLs whose state this run did not establish are in undetermined, never in unindexed. Read each one's status before wording the answer: it separates two cases a reader acts on differently. "We looked and could not tell" covers a failed inspection and a result the burst re-verify pass could not confirm. "skipped_quota" is the other case and means the opposite: we never got to look, because the property's daily budget ran out first. That is not a problem with the URL, needs no investigation, and is answered by running again tomorrow — say so rather than reporting it as a fault.

A refusal is {"ok": false, "error": <code>, "fix": <what to do>}, plus status when Search Console refused the call and detail (an exception type name) when the failure was unexpected.

gsc_auditA

The current indexation position for one property, as structured data.

Reads the local store only: no network call, no quota spent. It reports what the last inspection found, which is why the payload carries as_at — run gsc_find_unindexed first if the picture is stale, and check the stale count to see how much of it is.

Point-in-time by design. There are no movement numbers — nothing "moved to indexed", nothing "de-indexed" — because the store keeps no status history, and a zero in a field like that would read as a measurement that found no change rather than as an absence of data. basis says so in the payload.

Returns counts (total_known, checked, indexed, unindexed, undetermined, never_checked, stale), indexed_pct (null when nothing has been checked), a by_reason histogram over the ten reason codes, and three action counts: submittable, needs_site_access and no_action_needed. Every reason code is in exactly one of the three, so they sum to unindexed. submittable counts URLs a quota slot can move, not URLs to submit right now: it includes crawled-not-indexed, usually the largest bucket, where Google has already fetched the page and passed — a re-crawl can reverse that, but only after the page changes. Read each row's action before spending slots. Rendering all this — prose, table, chart — is yours to do.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mrshahidali420/google-search-console-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server