Skip to main content
Glama

SEO GSC Wizard MCP

Analyze keyword cannibalization

analyze_cannibalization
Read-only

Use this when the user asks whether several of their pages compete for the same query: finds such queries, ranked by a cannibalization score (Shannon entropy over the pages' impressions: higher means impressions split more evenly across pages, i.e. worse cannibalization), highest first, then by totalImpressions; each query's pages run most impressions first (page ctr is a 0-100 percentage). Not for a plain page-by-query table: use query_search_analytics. Only queries Google lists are analysed: on most properties a large share of clicks has no query row (analyze_sampling_impact measures it). Served from the GSC Wizard ClickHouse warehouse when available: it keeps only queries ranked by at least two pages before any cut, then scores them most impressions first, up to 10,000 queries or 25,000 page-query rows, whichever comes first. Each query lists at most 50 pages there; pageCount always covers every competing page, and the score is taken over the listed ones. totalClicks and totalImpressions are the query's own figures from Search Console's query view (on both sources), where a search showing two of the pages counts once, so they can be below the sum of the pages' figures, which count it once per page; minImpressions applies to the query's own figure. Otherwise the live Search Console API (see dataSource) reads the top 25,000 page-query rows by clicks, which drops zero-click competing pages first. truncated: true means the cut was hit; scanLimit is that cut (on the warehouse, the number of queries scored when truncated, else the query cap; on the live API, page-query rows). minImpressions defaults to 10; when it is left out and no query qualifies, the tool retries once at 1 on its own and says so in minImpressionsAutoRelaxed (minImpressions is always the floor applied). Date range is optional: omit startDate/endDate to analyze the last 28 settled days (data lags ~2-3 days).

Input Schema

NameRequiredDescriptionDefault
limitNo
endDateNoOptional. Window end as YYYY-MM-DD (e.g. 2026-05-31). You do NOT need to know today's date or the latest available date: omit this and the server uses the latest settled Search Console date (data lags ~2-3 days behind today). Never pass null or the string "null".
siteUrlYes
startDateNoOptional. Window start as YYYY-MM-DD (e.g. 2026-05-03). You do NOT need to know today's date. Omit BOTH startDate and endDate to analyze the last 28 settled days automatically (Search Console data lags ~2-3 days). Never pass null or the string "null".
searchTypeNoSearch type: web (default), image, video, news (the News tab), discover, googleNews (news.google.com), or all (web + image + video + news + discover combined, position impression-weighted, like the app's "All" filter). Discover and googleNews carry no query dimension, so query-level results for them are empty, and no position: it reads null, not 0.web
minImpressionsNoMinimum impressions per query (default 10). When left out and nothing passes it, the tool retries once at 1 and reports `minImpressionsAutoRelaxed`; a value you pass, including 10, is always applied as given.

Output Schema

NameRequiredDescriptionDefault
noteNo
rangeNo
thenByNo
listKeyNoName of the top-level field that holds the rows sortedBy describes.
siteUrlNo
warningNo
nextStepNo
orderingNo
sortedByNo
scanLimitNo
sortOrderNo
thenOrderNo
truncatedNo
dataSourceNo
partialDaysNo
cannibalizedNo
dataMaturityNo
emptyBecauseNo
methodLimitsNo
minImpressionsNo
settledThroughNo
warehouseLatestDateNo
minImpressionsAutoRelaxedNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed4 schema fields changed
    • removedInput schema / properties / minImpressions / default
      Removed value: -10
    • addedInput schema / properties / minImpressions / description
      Added value: +"Minimum impressions per query (default 10). When left out and nothing passes it, the tool retries once at 1 and reports `minImpressionsAutoRelaxed`; a value you pass, including 10, is always applied as given."
    • addedOutput schema / properties / listKey
      Added value: +{
      +  "$ref": "#/properties/siteUrl",
      +  "description": "Name of the top-level field that holds the rows sortedBy describes."
      +}
    • addedOutput schema / properties / minImpressionsAutoRelaxed
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": true,
      +      "properties": {
      +        "from": {
      +          "$ref": "#/properties/range/anyOf/0/properties/days"
      +        },
      +        "to": {
      +          "$ref": "#/properties/range/anyOf/0/properties/days"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ]
      +}
  2. Changed1 schema field changed
    • changedInput schema / properties / searchType / description
      Previous value: -"Search type: web (default), image, video, news (the News tab), discover, googleNews (news.google.com), or all (web + image + video + news + discover combined, position impression-weighted, like the app's \"All\" filter). Discover and googleNews carry no query dimension, so query-level results for them are empty."New value: +"Search type: web (default), image, video, news (the News tab), discover, googleNews (news.google.com), or all (web + image + video + news + discover combined, position impression-weighted, like the app's \"All\" filter). Discover and googleNews carry no query dimension, so query-level results for them are empty, and no position: it reads null, not 0."
  3. Changed2 schema fields changed
    • addedInput schema / properties / searchType / description
      Added value: +"Search type: web (default), image, video, news (the News tab), discover, googleNews (news.google.com), or all (web + image + video + news + discover combined, position impression-weighted, like the app's \"All\" filter). Discover and googleNews carry no query dimension, so query-level results for them are empty."
    • changedInput schema / properties / searchType / enum
      Previous value: -[
      -  "web",
      -  "image",
      -  "video",
      -  "news"
      -]New value: +[
      +  "web",
      +  "image",
      +  "video",
      +  "news",
      +  "discover",
      +  "googleNews",
      +  "all"
      +]
  4. Changed15 schema fields changed
    • changedOutput schema / additionalProperties
      Previous value: -falseNew value: +true
    • changedOutput schema / properties / cannibalized / items / additionalProperties
      Previous value: -falseNew value: +true
    • changedOutput schema / properties / cannibalized / items / properties / pages / items / additionalProperties
      Previous value: -falseNew value: +true
    • changedOutput schema / properties / dataMaturity / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "dateBasis": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "firstIncompleteDate": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "note": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "probe": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "properties": {
      -              "dataState": {
      -                "$ref": "#/properties/siteUrl"
      -              },
      -              "endDate": {
      -                "$ref": "#/properties/siteUrl"
      -              },
      -              "searchType": {
      -                "$ref": "#/properties/siteUrl"
      -              },
      -              "startDate": {
      -                "$ref": "#/properties/siteUrl"
      -              }
      -            },
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ]
      -      },
      -      "settledThrough": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "source": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "warehouseCutoff": {
      -        "$ref": "#/properties/siteUrl"
      -      }
      -    },
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": true,
      +    "properties": {
      +      "dateBasis": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "firstIncompleteDate": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "note": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "probe": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": true,
      +            "properties": {
      +              "dataState": {
      +                "$ref": "#/properties/siteUrl"
      +              },
      +              "endDate": {
      +                "$ref": "#/properties/siteUrl"
      +              },
      +              "searchType": {
      +                "$ref": "#/properties/siteUrl"
      +              },
      +              "startDate": {
      +                "$ref": "#/properties/siteUrl"
      +              }
      +            },
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ]
      +      },
      +      "settledThrough": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "source": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "warehouseCutoff": {
      +        "$ref": "#/properties/siteUrl"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedOutput schema / properties / emptyBecause
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / methodLimits
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / nextStep
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / ordering
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": {
      +        "additionalProperties": true,
      +        "properties": {
      +          "sortOrder": {
      +            "$ref": "#/properties/siteUrl"
      +          },
      +          "sortedBy": {
      +            "$ref": "#/properties/siteUrl"
      +          },
      +          "thenBy": {
      +            "$ref": "#/properties/siteUrl"
      +          },
      +          "thenOrder": {
      +            "$ref": "#/properties/siteUrl"
      +          }
      +        },
      +        "type": "object"
      +      },
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ]
      +}
    • addedOutput schema / properties / partialDays
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ]
      +}
    • changedOutput schema / properties / range / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "days": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "endDate": {
      -        "$ref": "#/properties/siteUrl"
      -      },
      -      "startDate": {
      -        "$ref": "#/properties/siteUrl"
      -      }
      -    },
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": true,
      +    "properties": {
      +      "days": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "endDate": {
      +        "$ref": "#/properties/siteUrl"
      +      },
      +      "startDate": {
      +        "$ref": "#/properties/siteUrl"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedOutput schema / properties / sortOrder
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / sortedBy
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / thenBy
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / thenOrder
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
    • addedOutput schema / properties / warning
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
  5. Changed1 schema field changed
    • addedOutput schema / properties / warehouseLatestDate
      Added value: +{
      +  "$ref": "#/properties/siteUrl"
      +}
  6. First observed

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnly/openWorld/non-destructive, and the description goes well beyond that: it discloses the two data sources (ClickHouse warehouse vs live Search Console API), each one's cut (10,000 queries / 25,000 page-query rows vs top 25,000 rows by clicks), the 50-page cap with pageCount covering unlisted pages, and the one-shot minImpressions relaxation reported via minImpressionsAutoRelaxed. It stops short of stating auth/permission requirements or expected latency, so it is not a full 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded correctly — purpose and ranking semantics come first, operational caveats after. But it is a single dense paragraph that partly duplicates schema text (minImpressions default of 10 and the auto-relax rule, the 28-day default window) and buries the warehouse-vs-live-API distinction mid-sentence, making it harder to scan than its content warrants.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with an output schema, this is more than complete: it explains the score's meaning, the per-page ordering, ctr as 0-100, the truncated/scanLimit semantics, and the known under-count of totalClicks/totalImpressions relative to page sums. Nothing an agent needs to call it correctly or interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 67%, and the description fills the important gaps: minImpressions applies to the query's own figure (not the sum of page figures), defaults to 10, and is auto-relaxed to 1 when nothing qualifies; date range is optional with a 28-day settled default. `limit` is never explained in either the description or the schema, which is the one remaining hole.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific trigger and verb+resource: 'Use this when the user asks whether several of their pages compete for the same query', and immediately defines the output ordering (cannibalization score from Shannon entropy over impressions, highest first, then totalImpressions). It explicitly names the sibling it is not (query_search_analytics), so the agent can disambiguate without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use ('whether several of their pages compete for the same query'), explicit when-not ('Not for a plain page-by-query table: use query_search_analytics'), and routes a related question to a third tool ('analyze_sampling_impact measures it'). Also states the default date window and that omitting dates is the normal path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources