Skip to main content
Glama
bcharleson

gsc-agent-cli

gsc-agent-cli

Headless Google Search Console for humans and AI agents. Everything you can see and do in the Search Console UI (search performance, indexing, sitemaps, URL inspection) as plain JSON from the terminal, plus the Core Web Vitals and PageSpeed data the Search Console UI shows but its API never exposes. One binary, one MCP server, the whole search-visibility picture.

npm install -g gsc-agent-cli
gsc snapshot site --site sc-domain:example.com --pretty
  • 🤖 Built for agents. Every command is also an MCP tool. Point Claude, Cursor, or any MCP client at gsc mcp and it can monitor and optimize a site's search presence autonomously.

  • 🧑‍💻 Built for humans. Sensible defaults, copy-paste recipes, JSON you can pipe into jq.

  • 🔭 Holistic. Combines owned-property GSC data (rankings, indexing, sitemaps) with outside-GSC signals (real-user Core Web Vitals via CrUX, lab performance via PageSpeed Insights): the two halves of "are we discoverable, and are we fast?"

  • 🪪 Headless-first auth. Service-account JSON for cron/agents, or OAuth to act as your own Google account.


Table of contents


Related MCP server: GSC MCP Server

Why this exists

The Search Console web UI is great for clicking around. It is terrible for automation: you can't put "is this URL indexed?" in a script, you can't diff last week's rankings in CI, and an AI agent can't read a chart. The official APIs can do these things, but they're split across several services with different auth models and awkward request shapes.

gsc-agent-cli collapses all of that into one tool with one mental model: one command per capability, JSON in and out, the same surface for a human typing and an agent calling MCP tools. That means an autonomous agent can now run the full loop a human SEO would: pull the performance report → find pages losing position → inspect their index status → check their Core Web Vitals → resubmit a sitemap → re-measure. Headlessly. On a schedule.


What it covers

Two planes of data behind one binary:

Plane 1: Inside Search Console (your verified properties)

Requires OAuth or a service account with access to the property.

Command

Search Console API method

What you get

gsc sites list

sites.list

Every property you can access + permission level

gsc sites get

sites.get

One property's details

gsc sites add / delete

sites.add / delete

Manage properties in the account

gsc sitemaps list

sitemaps.list

All sitemaps + errors, warnings, indexed counts

gsc sitemaps get

sitemaps.get

One sitemap's full status

gsc sitemaps submit / delete

sitemaps.submit / delete

(Re)submit or remove a sitemap

gsc search-analytics query

searchanalytics.query

Clicks, impressions, CTR, position by query/page/country/device/date

gsc search-analytics top-queries

wrapper

Top queries by clicks

gsc search-analytics top-pages

wrapper

Top pages by clicks

gsc inspect url

urlInspection.index.inspect

Index status, last crawl, canonical, mobile usability, rich results

gsc indexing report

composite

Reconstructs the "indexed vs not indexed" overview by inspecting sitemap URLs

Plane 2: Outside Search Console (any public URL, just an API key)

These fill the gaps the Search Console API leaves open, most importantly the Experience / Core Web Vitals section the UI shows but the API does not return.

Command

API

What you get

gsc crux query

Chrome UX Report

Real-user Core Web Vitals (LCP, INP, CLS, FCP, TTFB) for an origin or page

gsc pagespeed run

PageSpeed Insights

Lab Lighthouse scores (performance, SEO, accessibility, best-practices) + field data

Plane 1 + 2 fused

Command

What you get

gsc snapshot site

One call → permission level, sitemap health, search totals, top queries, and real-user Core Web Vitals. The whole health picture, degrading gracefully if any source is missing.


Install

Global (recommended):

npm install -g gsc-agent-cli
gsc --version

Without installing (npx):

npx gsc-agent-cli sites list

From source (for development / before npm publish):

git clone https://github.com/bcharleson/gsc-agent-cli.git
cd gsc-agent-cli
npm install
npm run build
npm link            # makes `gsc` available globally

Requires Node.js ≥ 18.


Setup (step by step)

You need credentials for the property you want to read. Pick one of two methods. For agents, cron jobs, and CI, use a service account. For interactive use as yourself, use OAuth.

First, know your property URL format. Search Console has two kinds of property, and the string you pass as --site differs:

  • Domain propertysc-domain:example.com (covers all subdomains + http/https)

  • URL-prefix propertyhttps://www.example.com/ (exact prefix, trailing slash matters)

Run gsc sites list after setup to see the exact strings you have access to.

A service account is a non-human Google identity with its own key file. No browser, no token refresh, works headless forever. ~3 minutes.

1. Create a Google Cloud project (or reuse one)

2. Enable the APIs

  • APIs & Services → Library, then enable each of these (search the name, click Enable):

    • Google Search Console API for all Plane 1 commands

    • Chrome UX Report API for gsc crux query

    • PageSpeed Insights API for gsc pagespeed run

3. Create the service account

  • APIs & Services → Credentials → Create credentials → Service account.

  • Name it (e.g. gsc-cli) → Create and continue → skip the optional role/grant steps → Done.

4. Download its JSON key

  • Click the new service account → Keys → Add key → Create new key → JSON → Create.

  • A .json file downloads. Keep it private. Move it somewhere stable, e.g.:

    mkdir -p ~/.config/gsc-keys
    mv ~/Downloads/seo-tools-*.json ~/.config/gsc-keys/gsc-sa.json
    chmod 600 ~/.config/gsc-keys/gsc-sa.json

5. ⚠️ Grant the service account access to your property (the step everyone forgets)

  • Copy the service account's email (it looks like gsc-cli@seo-tools.iam.gserviceaccount.com, visible on the Credentials page).

  • Go to Search Console → select your property → Settings (gear) → Users and permissions → Add user.

  • Paste the service account email, set permission to Full (or Restricted for read-only), → Add.

  • Without this, every Plane 1 call returns 403. Plane 2 (CrUX/PageSpeed) does not need it.

6. Point the CLI at the key

export GOOGLE_APPLICATION_CREDENTIALS=~/.config/gsc-keys/gsc-sa.json
# Or persist it so you don't need the env var every shell:
gsc config use-key-file ~/.config/gsc-keys/gsc-sa.json

7. Set a default property so you can omit --site everywhere

gsc config set-site sc-domain:example.com

Done. Jump to Verify your setup.

Option B: OAuth (act as your own Google account)

Use this when you want the tool to see every property your Google login can access, with no per-property grant. Involves a one-time browser consent.

1. Create an OAuth client

  • Google Cloud Console → APIs & Services → Credentials → Create credentials → OAuth client ID.

  • If prompted, configure the consent screen (External, add your email as a test user).

  • Application type: Desktop appCreate.

  • Add this authorized redirect URI: http://localhost:4280/oauth2callback.

  • Copy the Client ID and Client secret.

2. Log in

gsc login --client-id <CLIENT_ID> --client-secret <CLIENT_SECRET>

A URL prints; open it, approve, and the loopback server captures the token automatically. The refresh token is stored in ~/.gsc/config.json (mode 600).

Tip: set GSC_OAUTH_CLIENT_ID / GSC_OAUTH_CLIENT_SECRET and just run gsc login.

API key for Core Web Vitals & PageSpeed

crux query and pagespeed run use a simple API key, independent of the GSC auth above.

  • Google Cloud Console → APIs & Services → Credentials → Create credentials → API key → copy it.

  • Make sure the Chrome UX Report API and PageSpeed Insights API are enabled (step A.2).

export GOOGLE_API_KEY=<your-key>
# or persist it:
gsc config set-api-key <your-key>

(PageSpeed works without a key but is heavily rate-limited; CrUX requires one.)

Verify your setup

gsc status              # shows active credential source and confirms it by listing properties
gsc sites list --pretty # lists every property you can access (and the exact --site strings)
gsc config show         # shows default site, key file, oauth, api key state

If gsc status reports verified: true with an accessibleProperties count, you're ready.


Command reference

All commands print JSON to stdout. Global flags (below) work on every command.

Auth & config

gsc login [--client-id <id>] [--client-secret <secret>]   # OAuth flow
gsc logout                                                # remove stored credentials
gsc status [--key-file <path>]                            # show + verify active credentials
gsc config set-site <siteUrl>                             # default property
gsc config use-key-file <path>                            # default service-account key
gsc config set-api-key <key>                              # default Google API key (CrUX/PSI)
gsc config show                                           # current config (secrets redacted)

Sites

gsc sites list
gsc sites get [site] [--site <siteUrl>]
gsc sites add <site>
gsc sites delete <site>

Sitemaps

gsc sitemaps list [--site <siteUrl>] [--sitemap-index <url>]
gsc sitemaps get <feedpath> [--site <siteUrl>]
gsc sitemaps submit <feedpath> [--site <siteUrl>]
gsc sitemaps delete <feedpath> [--site <siteUrl>]

Search analytics (the performance report)

gsc search-analytics query [options]
  --site <siteUrl>             # property (or use default)
  -d, --dimensions <list>      # query,page,country,device,date,searchAppearance
  --days <n>                   # lookback window (default 28)
  --start-date <YYYY-MM-DD>    # explicit start (overrides --days)
  --end-date <YYYY-MM-DD>      # explicit end (default ~2 days ago for data lag)
  --type <type>                # web|image|video|news|discover|googleNews
  --filters <json>             # dimensionFilterGroups as JSON
  -l, --row-limit <n>          # max rows (default 1000, max 25000)
  --start-row <n>              # pagination offset
  --aggregation-type <type>    # auto|byPage|byProperty
  --data-state <state>         # final|all  (all = include fresh, incomplete data)

gsc search-analytics top-queries [--days <n>] [-l <n>] [--site <siteUrl>]
gsc search-analytics top-pages   [--days <n>] [-l <n>] [--site <siteUrl>]

URL inspection

gsc inspect url <url> [--site <siteUrl>] [--language <bcp47>]

Indexing report (composite)

gsc indexing report [options]
  --site <siteUrl>
  --sitemap <url>        # which sitemap to read URLs from (defaults to first listed)
  -l, --limit <n>        # max URLs to inspect (default 100; API cap 2000/day)
  --concurrency <n>      # parallel inspections (default 5, max 10)
  --details              # include the per-URL breakdown, not just the summary

Core Web Vitals (CrUX) & PageSpeed

gsc crux query (--origin <origin> | --url <url>) [--form-factor PHONE|TABLET|DESKTOP|ALL_FORM_FACTORS] [--api-key <key>]
gsc pagespeed run --url <url> [--strategy mobile|desktop] [--category <list>] [--raw] [--api-key <key>]

Holistic snapshot

gsc snapshot site [--site <siteUrl>] [--origin <origin>] [--days <n>] [--top-limit <n>] [--api-key <key>]

MCP server

gsc mcp     # start the stdio MCP server (exposes every command as a tool)

Recipes

# Top 25 queries last 28 days, pretty-printed
gsc search-analytics top-queries --days 28 -l 25 --pretty

# Clicks/impressions by page AND query for a fixed month
gsc search-analytics query -d page,query --start-date 2026-05-01 --end-date 2026-05-31

# Only US traffic, by query
gsc search-analytics query -d query \
  --filters '[{"filters":[{"dimension":"country","expression":"usa"}]}]'

# Which of my queries are stuck on page 2? (position 11-20), with jq
gsc search-analytics query -d query -l 1000 \
  | jq '.rows[] | select(.position > 10 and .position <= 20) | {query: .keys[0], position, impressions}'

# Is a specific page indexed?
gsc inspect url https://www.example.com/pricing \
  | jq '.inspectionResult.indexStatusResult.coverageState'

# What share of my sitemap is actually indexed?
gsc indexing report --limit 200 --pretty

# Real-user Core Web Vitals for the whole site (mobile)
gsc crux query --origin https://www.example.com --form-factor PHONE --pretty

# Lab performance + SEO score for the homepage
gsc pagespeed run --url https://www.example.com --category performance,seo --pretty

# The whole health picture in one call
gsc snapshot site --pretty

# Resubmit a sitemap after a deploy
gsc sitemaps submit https://www.example.com/sitemap.xml

Using it from an AI agent (MCP)

Every command is exposed as an MCP tool by gsc mcp. Add it to your MCP client config:

{
  "mcpServers": {
    "gsc": {
      "command": "gsc",
      "args": ["mcp"],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "/absolute/path/to/gsc-sa.json",
        "GOOGLE_API_KEY": "your-crux-pagespeed-key"
      }
    }
  }
}

(Or "command": "npx", "args": ["gsc-agent-cli", "mcp"] if not installed globally.)

Tool names (snake_case) map 1:1 to the commands:

Tool

Tool

Tool

sites_list

sitemaps_list

search_analytics_query

sites_get

sitemaps_get

top_queries

sites_add

sitemaps_submit

top_pages

sites_delete

sitemaps_delete

inspect_url

indexing_report

crux_query

pagespeed_run

snapshot_site

Contract for agents:

  • Inputs are JSON objects matching each tool's schema (e.g. {"dimensions": "query", "days": 28}). Read-only tools are annotated readOnlyHint: true.

  • Every tool returns a JSON string. Errors return {"error": "...", "code": "..."} with isError: true.

  • Property URLs use sc-domain:example.com or https://www.example.com/. Set a default with gsc config set-site so the agent can omit site.

  • See AGENTS.md for a condensed agent playbook.


How it helps you get discovered & optimized

The point of Search Console is the two questions every site owner actually cares about. This tool answers both, end to end, without a human in the loop:

1. "Is Google finding and indexing my content?" (discovery)

  • sitemaps submit + sitemaps list → make sure Google has your URL inventory and it's downloading cleanly.

  • indexing report → know what fraction of your sitemap is actually indexed, and why the rest isn't (coverageState).

  • inspect url → debug any single page: crawled? indexed? canonical correct? blocked by robots?

2. "Am I ranking, and is the experience good enough to keep ranking?" (optimization)

  • search-analytics query → the raw signal: which queries and pages drive clicks/impressions, and at what position. Diff it over time to catch decay.

  • top-pages / top-queries → where to focus.

  • crux query → real-user Core Web Vitals, a confirmed ranking input. The GSC API hides this; this tool surfaces it.

  • pagespeed run → lab diagnosis of why a page is slow, with concrete Lighthouse opportunities.

Because all of this is headless JSON, an agent can run it on a cron, store the history, and act: detect a page dropping from position 4 to 9, confirm it's indexed, notice its LCP regressed after a deploy, and open a ticket (or fix it). snapshot site is the one-shot version of that whole sweep.


Output, flags & exit codes

Global flags (work on every command):

Flag

Effect

--pretty

Indented JSON (default is compact, one line)

--output json|pretty

Same as above, explicit

--quiet

Suppress stdout; rely on exit code

--fields <a,b,c>

Keep only these fields (projects into rows/siteEntry/sitemap arrays)

--key-file <path>

Service-account key for this invocation (overrides env + config)

Exit codes: 0 success, 1 error. Errors print {"error","code"} to stderr. Codes include AUTH_ERROR, NOT_FOUND, VALIDATION_ERROR, RATE_LIMIT, API_ERROR.

Credential resolution order: --key-fileGOOGLE_APPLICATION_CREDENTIALSGSC_SERVICE_ACCOUNT_JSON (inline) → stored key file → stored OAuth token.

Environment variables: GOOGLE_APPLICATION_CREDENTIALS, GSC_SERVICE_ACCOUNT_JSON, GOOGLE_API_KEY, GSC_OAUTH_CLIENT_ID, GSC_OAUTH_CLIENT_SECRET. Config lives at ~/.gsc/config.json.


Rate limits & gotchas

  • Service account must be added as a property user or every Plane 1 call returns 403. (Setup step A.5.)

  • URL Inspection is limited to 2,000 queries/day and 600/min per property. indexing report fans out over it, so always cap with --limit.

  • Search analytics data lags ~2 days. Defaults end the range 2 days ago; use --data-state all to include fresh (incomplete) data.

  • CrUX needs enough traffic. Low-traffic origins/pages return no record. That's a data-availability limit, not an error.

  • CrUX requires an API key; PageSpeed works keyless but is rate-limited (you'll see a quota error fast without a key).


What is not possible

Honesty about the gaps, because they shape what you can automate:

  • The "Recommendations" cards in the GSC UI have no public API. Not available here.

  • The Experience / Core Web Vitals report inside the Search Console API does not exist, which is exactly why this tool reaches out to CrUX and PageSpeed instead.

  • The aggregate "why not indexed" breakdown isn't a single endpoint; indexing report reconstructs it by inspecting URLs individually (hence the rate limit).

  • Live SERP positions / rank tracking for arbitrary keywords is not a Google API. Search Console only reports queries your site already appears for.


Architecture

For contributors and agents reading the source:

  • One source of truth. Every capability is a CommandDefinition (in src/commands/**). The array in src/commands/index.ts is iterated by both the CLI registrar and the MCP server (src/mcp/server.ts), so a command added once appears in both surfaces automatically.

  • Two auth planes, lazily resolved. src/core/client.ts resolves GSC OAuth/service-account auth only on first owned-property call; key-based commands (src/core/web.ts) never trigger it. This is why gsc pagespeed run works with just an API key.

  • Stack: TypeScript, commander, zod (schemas drive both CLI validation and MCP tool input schemas), googleapis, @modelcontextprotocol/sdk, built with tsup.

npm run dev          # run from source (tsx)
npm run typecheck    # tsc --noEmit
npm run build        # bundle to dist/
npm test             # vitest

License

MIT © Top of Funnel

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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/bcharleson/gsc-agent-cli'

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