gsc-agent-cli
Enables querying Google Search Console data, Core Web Vitals, and PageSpeed insights from the terminal or via MCP tools.
README
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 mcpand 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
- Why this exists
- What it covers
- Install
- Setup (step by step)
- Command reference
- Recipes
- Using it from an AI agent (MCP)
- How it helps you get discovered & optimized
- Output, flags & exit codes
- Rate limits & gotchas
- What is not possible
- Architecture
- License
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
--sitediffers:
- Domain property →
sc-domain:example.com(covers all subdomains + http/https)- URL-prefix property →
https://www.example.com/(exact prefix, trailing slash matters)Run
gsc sites listafter setup to see the exact strings you have access to.
Option A: Service account (recommended for agents & automation)
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)
- Go to console.cloud.google.com → project picker (top bar) → New Project → name it (e.g.
seo-tools) → Create.
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
.jsonfile 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 app → Create.
- 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_SECRETand just rungsc 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 annotatedreadOnlyHint: true. - Every tool returns a JSON string. Errors return
{"error": "...", "code": "..."}withisError: true. - Property URLs use
sc-domain:example.comorhttps://www.example.com/. Set a default withgsc config set-siteso the agent can omitsite. - See
AGENTS.mdfor 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-file → GOOGLE_APPLICATION_CREDENTIALS → GSC_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 reportfans out over it, so always cap with--limit. - Search analytics data lags ~2 days. Defaults end the range 2 days ago; use
--data-state allto 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 reportreconstructs 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(insrc/commands/**). The array insrc/commands/index.tsis 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.tsresolves GSC OAuth/service-account auth only on first owned-property call; key-based commands (src/core/web.ts) never trigger it. This is whygsc pagespeed runworks 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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.