| 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 five checks in order — oauth_client, token, config, store,
properties — 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, or a raw response body. Every
failing check carries a non-empty fix string with a concrete next
step; this tool diagnoses, it does not repair anything itself. Costs at most one Search Console API call (sites.list, for the
properties check) — zero network calls if an earlier check already
shows the client or token is unusable and a caller stops before
reaching it, though this implementation always runs all five. 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"}. "submission" is the Request-Indexing slot budget: {"free", "spendable_free", "used", "slots", "daily_reserve", "next_free_at"}.
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),
"inspection_daily", or "inspection_minute" — or None when every budget
has headroom. |
| 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": ...}. |