IndexScout
Ask your agent what to work on in search, and get answers backed by exact Google Search Console evidence.
IndexScout is a local, read-only MCP server for Google Search Console (GSC). It does more than return API rows: it compares equal periods, splits traffic changes by page, query, device, country, and search type, ranks opportunities on pages you already have, classifies cannibalization, and audits indexing. Every answer carries the periods, metrics, formulas, and caveats an agent needs to stay honest.
You: What should I work on in search this week?
Agent: gsc_site_snapshot → gsc_diagnose_change → gsc_find_opportunities → gsc_page_analysis
"1. /pricing lost 412 clicks (2026-08-29..2026-09-21 vs the prior 24 days) ..."
- Read-only by construction — only the
webmasters.readonlyscope; no write or Indexing API code. - Local — stdio only, no network listener, no telemetry, tokens in your OS keyring.
- Agent-first — every response has
summary,evidence,results,warnings,limitations,recommended_next_calls, andprovenance.
IndexScout is an independent open-source project. It is not affiliated with or endorsed by Google, OpenAI, or Anthropic.
Contents
What you can ask · Example conversations · Install · OAuth setup · Service accounts · Codex · Claude · Other clients · Tools · Security · Limitations · Troubleshooting · Contributing
What you can ask
| Goal | Ask | Tools the agent uses |
|---|---|---|
| Decide what to work on | "What should I work on in search this week?" | gsc_site_snapshot, gsc_find_opportunities |
| Diagnose a traffic drop | "Why did organic clicks fall last week?" | gsc_diagnose_change |
| Check a deploy | "What changed since we shipped the new nav on Sept 2?" | gsc_diagnose_change(start_date=...) |
| Analyze one page | "What is happening with /pricing?" | gsc_page_analysis |
| Analyze one search | "How do we perform for 'moving permit cost'?" | gsc_query_analysis |
| Find ranking opportunities | "Which queries rank 4–20 with real impressions?" | gsc_find_opportunities |
| Weak CTR | "Which high-impression pages have unusually weak CTR?" | gsc_find_opportunities(category="low_ctr") |
| Cannibalization | "Is one query split across competing pages?" | gsc_find_cannibalization |
| Indexing | "Which important URLs are not indexed?" | gsc_indexing_audit |
| Sitemap coverage | "Are the URLs in my sitemap indexed?" | gsc_indexing_audit(source="sitemap") |
| Devices | "Are mobile results behaving differently from desktop?" | gsc_diagnose_change, gsc_site_snapshot |
| Portfolio | "Which properties need attention?" | gsc_list_properties, gsc_site_snapshot |
| Evidence | "What evidence supports this recommendation?" | gsc_search_analytics |
Example conversations
All data below is fabricated for illustration. The domain and numbers are examples.
Weekly review
You: What should I work on in search this week for sc-domain:example.com?
Agent: (gsc_site_snapshot days=7 → gsc_find_opportunities → gsc_page_analysis)
Web search 2026-09-15..2026-09-21 vs 2026-09-08..2026-09-14 (final data, PT):
clicks 8,940 (−6.2%), impressions 412,300 (+1.8%), CTR 2.2% (−0.18 pp).
1. Review the title and SERP intent for /boston-moving-permit: 3,420 impressions,
0.8% CTR, average position 6.2; CTR down 31% vs the prior week. The property's
own CTR at positions 6–10 is 2.9%. Check the live results page first — a map pack
or ads could explain it. (score 71.8, category low_ctr)
2. /guides/moving-checklist averages position 11.4 for "moving checklist"
(5,100 impressions). It is just below page one; review coverage and internal links.
3. "permit cost" is shown for both /pricing and /permit-cost, and impression share
moved 38 points to /permit-cost. Classified possible_competition — worth a content
and intent review, not an automatic merge.
What the data does not show: why CTR fell. GSC reports associations, not causes.
Traffic drop
You: Organic traffic fell. What happened?
Agent: (gsc_diagnose_change metric=clicks)
Clicks fell by 1,210 (−14.8%), 2026-08-25..2026-09-21 vs 2026-07-28..2026-08-24.
- 62% of the loss is on /pricing (−750 clicks); its average position moved 1.4 → 2.3.
- By query+page: "pricing" → /pricing −610 clicks.
- Devices: desktop −980, mobile −230. Countries: loss concentrated in usa.
- Returned query rows explain 81% of the change; the rest is anonymized or truncated.
Next I will inspect /pricing's index status (gsc_indexing_audit source=losing_pages).
I cannot tell from GSC alone whether this is a site change, competition, or demand.
Install
Requires uv. IndexScout runs on Python 3.11+ (uv installs it if needed).
# Run without installing
uvx indexscout doctor
# Or put the `indexscout` command on your PATH
uv tool install indexscout
MCP configs below pin a version (indexscout@0.2.0) so an upgrade is always a deliberate change.
Development install:
git clone https://github.com/jpow18/indexscout && cd indexscout
uv sync
uv run indexscout doctor
Then set up OAuth (below), and run:
indexscout auth login # opens a browser once
indexscout doctor # checks deps, token storage, scope, API reachability, MCP
indexscout properties # lists exact property identifiers
indexscout snapshot sc-domain:example.com --days 28
OAuth setup
Desktop OAuth is the recommended default. You create your own OAuth client, so no third party ever holds your token.
- In the Google Cloud console, create or select a project.
- Enable the Google Search Console API (APIs & Services → Library).
- Configure the OAuth consent screen (Google Auth Platform): user type External is fine for a
personal Google account. Add your Google account as a test user. Add the scope
https://www.googleapis.com/auth/webmasters.readonly. - Create credentials: OAuth client ID → Desktop app. Download the JSON.
- Save it as
client_secret.jsonin the IndexScout config directory, or pointINDEXSCOUT_CLIENT_SECRETSat it:- Linux:
~/.config/indexscout/client_secret.json - macOS:
~/Library/Application Support/indexscout/client_secret.json - Windows:
%LOCALAPPDATA%\indexscout\indexscout\client_secret.json
- Linux:
- Run
indexscout auth loginand approve read-only access in the browser.
The refresh token goes to your OS keyring (Secret Service, macOS Keychain, or Windows Credential
Locker). Without a usable keyring, IndexScout writes a 0600 token file and prints a warning.
Access tokens refresh automatically in memory.
While the OAuth app is in Testing, Google expires refresh tokens after 7 days. Run
indexscout auth loginagain, or publish the app to In production for your own use.
indexscout auth logout deletes the stored token. Revoke access at
https://myaccount.google.com/permissions.
Service accounts
For unattended automation (CI, scheduled reports):
- Create a service account in Google Cloud and download a JSON key. Keep the key outside any repository.
- Enable the Search Console API in that project.
- In Search Console, open each property → Settings → Users and permissions → Add user, enter the service account's email, and choose Restricted (read access is enough).
- Set
INDEXSCOUT_SERVICE_ACCOUNT_FILE=/path/to/key.json. IndexScout requests only the read-only scope. The service account sees only properties you added it to.
Configuration
| Variable | Purpose |
|---|---|
INDEXSCOUT_ALLOWED_PROPERTIES |
Comma-separated exact identifiers (sc-domain:example.com,https://www.example.com/). Everything else is rejected, even if the account can access it. |
INDEXSCOUT_CLIENT_SECRETS |
Path to the Desktop OAuth client JSON. |
INDEXSCOUT_SERVICE_ACCOUNT_FILE |
Use a service account instead of OAuth. |
INDEXSCOUT_TOKEN_STORE |
auto (default), keyring, or file. |
INDEXSCOUT_CONFIG_DIR |
Override the config directory. |
See .env.example. No .env file is required.
Codex setup
Plugin (MCP server + skill):
codex plugin marketplace add jpow18/indexscout
codex plugin add indexscout@indexscout
MCP server only:
codex mcp add indexscout -- uvx indexscout@0.2.0 serve
Copy skills/indexscout to ~/.codex/skills/ if you want the skill without
the plugin.
Claude setup
Claude Code plugin (MCP server + skill):
claude plugin marketplace add jpow18/indexscout
claude plugin install indexscout@indexscout
Claude Code MCP only:
claude mcp add --scope user indexscout -- uvx indexscout@0.2.0 serve
Claude Desktop: download indexscout.mcpb from the
latest release and open it. Run
indexscout auth login in a terminal first. You can build the bundle yourself with
scripts/build_mcpb.sh.
Other MCP clients
Any stdio MCP client works (examples/mcp.json):
{
"mcpServers": {
"indexscout": {
"command": "uvx",
"args": ["indexscout@0.2.0", "serve"]
}
}
}
Agent workflow
The server sends instructions at initialization, every tool description says when to use it, and
the bundled indexscout skill teaches the workflows: weekly review,
traffic-loss diagnosis, page evaluation, opportunity finding, cannibalization, indexing audits, and
how to avoid unsupported SEO conclusions.
A typical chain for "What should I work on?":
gsc_capabilities→ finds the property (only if unknown)gsc_site_snapshot→ totals, gains, losses, device and search-type changesgsc_diagnose_change→ only if clicks or impressions fell ≥ 10%gsc_find_opportunities→ ranked, scored items with evidencegsc_page_analysis→ validates the top items
Each step returns recommended_next_calls with ready-to-use arguments.
Tool reference
All tools are read-only and return the same envelope:
{
"summary": "...", // one or two sentences with exact numbers and periods
"evidence": ["..."], // metric statements that support the summary
"results": {}, // structured, bounded data
"warnings": ["..."], // truncation, incomplete data, partial failures
"limitations": ["..."], // what this data cannot show
"recommended_next_calls": [{"tool": "...", "arguments": {}, "reason": "..."}],
"provenance": {} // property, inclusive PT date ranges, data state, dimensions,
// filters, rows returned, truncation, formulas, units
}
| Tool | Answers | Notes |
|---|---|---|
gsc_capabilities |
What can IndexScout do here? | Auth status, scopes, allowlist, properties, workflows, limits, first call. No secrets. |
gsc_list_properties |
Which properties exist? | Exact identifier, permission level, allowed by local policy. |
gsc_search_analytics |
Give me the rows. | Explicit dates, 6 dimensions, 6 search types, all filter operators, final/all, startRow pagination, ≤ 1,000 rows per call. |
gsc_site_snapshot |
How is this property doing? | Equal-period totals and deltas, top gaining/losing pages and queries, devices, search types, data-quality warnings. |
gsc_diagnose_change |
Why did clicks/impressions change? | Decomposes by page, query, query+page, device, country, search type. New and absent keys. Never claims cause. |
gsc_find_opportunities |
What should I work on? | Positions 4–20, low CTR, near page one / top 3, impressions rising without clicks. Documented score. |
gsc_page_analysis |
What is happening with this page? | Trend, exact-page queries, gains/losses, devices, appearances, position distribution, shared queries, optional inspection. |
gsc_query_analysis |
How do we do for this search? | Pages, trend, device/country, competing pages, new/growing/declining/stable label. |
gsc_find_cannibalization |
Is a query split across pages? | possible_competition, likely_intent_split, or review, with rules in provenance. |
gsc_inspect_url |
Is this URL indexed? | Verdict, coverage, crawl, canonicals, robots, fetch, referrers, sitemaps. Indexed version, not live. |
gsc_indexing_audit |
Which important URLs have problems? | ≤ 50 URLs from a list, top pages, losing pages, or live sitemap files (URLs with no impressions first); grouped; partial failures kept. |
gsc_list_sitemaps |
Are sitemaps healthy? | Submission/download dates, pending, errors, warnings, content counts. |
Opportunity scoring
potential_clicks = impressions × max(0, target_ctr − current_ctr)
score = potential_clicks × (1 + trend_adjustment)
target_ctr is the property's own impression-weighted CTR for the target position bucket (1, 2, 3,
4–5, 6–10, 11–20, 21+). Striking-distance pairs target the next better bucket; low-CTR pairs target
their own bucket. If a bucket has fewer than 1,000 impressions, a conservative default curve is used
and labeled default_curve. trend_adjustment is +0.2 when impressions grew ≥ 20% versus the
baseline, −0.2 when they fell ≥ 20%, else 0. The score ranks work; it is not a traffic forecast.
Data rules IndexScout applies
- Equal-length periods; unequal ones need
allow_unequal_periods=true. - Dates are inclusive calendar dates in America/Los_Angeles, as GSC defines them.
- Incomplete dates are excluded by default using Google's
firstIncompleteDate. - Aggregate CTR = clicks ÷ impressions; aggregate position is impression-weighted.
- Queries are attributed to pages only from query+page rows.
- Keys missing from returned rows are reported as absent, not zero.
Security model
- Requests only
webmasters.readonly. No site, sitemap, or URL submission code; no Indexing API. - stdio transport only; no network listener (OAuth login uses a one-time localhost redirect).
- Refresh token in the OS keyring, or an atomic
0600file with a warning; access tokens in memory. - Tokens, codes, client secrets, API responses, queries, URLs, and property data are never logged.
- Optional exact property allowlist; URL Inspection targets must belong to the property.
- The only non-Google request is a GET of a sitemap file inside the property (for sitemap audits). Redirects must stay inside the property; files are size-limited; XML with DTDs is rejected.
- GSC strings are sanitized, quoted, and labeled untrusted; agents are told never to follow them.
Details: SECURITY.md and docs/threat-model.md.
Google API limitations
- Final data usually lags 2–3 days; the newest dates stay incomplete until finalized.
- About 16 months of history.
- Anonymized (rare) queries are never returned, so query rows do not sum to totals.
- At most 25,000 rows per request, sorted by clicks, and a limited number of rows per day; IndexScout fetches one bounded page per analysis and flags possible truncation.
- Discover and Google News have no query dimension and no position.
- URL Inspection shows Google's indexed version, not a live test, and allows about 2,000 inspections per property per day (600 per minute).
- The API does not list the URLs inside a sitemap. IndexScout reads the live sitemap files from your site instead, so the audit uses the current file, which can differ from the version Google last read.
- Quota errors (HTTP 429) usually clear after about 15 minutes.
Troubleshooting
| Symptom | Fix |
|---|---|
| "Not authenticated" | Run indexscout auth login in a terminal. MCP servers cannot open a browser for you. |
| "OAuth client file not found" | Save the Desktop client JSON to the path shown, or set INDEXSCOUT_CLIENT_SECRETS. |
| "Google rejected the stored refresh token" | The token expired (Testing apps: 7 days) or was revoked. Log in again. |
| 403 on a property | The account lacks access, or the identifier is wrong. Use indexscout properties. |
| "not in INDEXSCOUT_ALLOWED_PROPERTIES" | Add the exact identifier to the allowlist, or remove the variable. |
| "does not belong to property" | Inspect URLs covered by the property: same scheme/host/port for URL-prefix properties. |
| Keyring warning on Linux | Install and unlock a Secret Service provider (GNOME Keyring or KWallet), or accept the 0600 file. |
| Server not showing in a client | Run indexscout doctor, then check the client's MCP logs. uv must be on the client's PATH. |
Contributing
See CONTRIBUTING.md. Tests use a fake Search Console, so no credentials are needed. Please keep IndexScout read-only and evidence-first.
Acknowledgements
The tool coverage was informed by AminForou/mcp-gsc (MIT), used only as a behavioral reference. IndexScout is an independent implementation and shares no code with it.
License
MIT © 2026 James Pow
Release files for indexscout 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| indexscout-0.2.0.tar.gz | 61.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| indexscout-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 111.6 kB
Release files / indexscout-0.2.0.tar.gz
| Download URL | indexscout-0.2.0.tar.gz |
|---|---|
| Size | 61.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e01dac30c089d5afc4fdfdf6517a2dc9602f170ef5c3251aaed4cdf18fe7a418
|
|
BLAKE2b-256 checksum How to use checksums |
37f61fd1a5348f862db7959b12b7ed1057766c66c73f39c42b2beff4717ea109
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / indexscout-0.2.0-py3-none-any.whl
| Download URL | indexscout-0.2.0-py3-none-any.whl |
|---|---|
| Size | 49.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ab81669cf2f6857bbb19c63e5909a00abcbb93686c32eacc039eeaf7832f2a6e
|
|
BLAKE2b-256 checksum How to use checksums |
0b5178fbbf2614802b79ea73f7da64b56992e73ae105fb91627be51c103b28f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log