@digestseo/mcp-gsc 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.9] - 2026-09-21
11
+
12
+ ### Changed
13
+ - `sites.list` now marks whether each returned property identifier matches the URL-prefix / `sc-domain:` forms currently documented by the Search Console API, and the docs explicitly avoid guessing an undocumented API identifier for Search Console platform properties.
14
+
15
+ ### Fixed
16
+ - `analytics.query` now exposes `position_supported: false` plus an explicit note for Google Discover responses so agents do not interpret the generic row `position` field as a supported Discover metric.
17
+
8
18
  ## [0.4.8] - 2026-09-21
9
19
 
10
20
  ### Fixed
package/README.md CHANGED
@@ -75,7 +75,7 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-onl
75
75
  | **`server.capabilities`** | Read | List every tool this server exposes and report whether your Google connection is currently authenticated (`connected` / `not_connected`). Takes no arguments — a good first call for discovery. |
76
76
  | **`sites.list`** | Read | List the Search Console properties the connected Google account can access (`siteUrl`, `permissionLevel`). |
77
77
  | **`sites.get`** | Read | Retrieve one exact Search Console property and the connected account's permission level for it. |
78
- | **`analytics.query`** | Read | Impressions, clicks, CTR, and average position over a date range, with dimensions, filters, safe pagination, and selectable search type. Pass `dimensions: []` for true site totals; Google's aggregate row may omit `keys`. Search Analytics does not guarantee every data row and can return only top rows, so exhausting local pagination is not proof that the provider dataset is exhaustive. |
78
+ | **`analytics.query`** | Read | Impressions, clicks, CTR, and average position over a date range, with dimensions, filters, safe pagination, and selectable search type. Pass `dimensions: []` for true site totals; Google's aggregate row may omit `keys`. Search Analytics does not guarantee every data row and can return only top rows, so exhausting local pagination is not proof that the provider dataset is exhaustive. For `search_type: "discover"`, `position_supported` is `false` because Google Discover does not support average position. |
79
79
  | **`insights.page_queries`** / **`insights.query_pages`** | Read | Drill from one exact page to its Search Console queries, or from one exact query to the pages Google surfaced for it, with `row_limit` / `start_row` pagination. |
80
80
  | **`urls.inspect`** | Read | Google's URL Inspection report for the version of one page currently known in Google's index. It is not a live URL test; mobile-usability output is deprecated. |
81
81
  | **`urls.inspect_many`** | Read | Inspect Google's indexed versions of up to 10 URLs sequentially in one call. Each URL still consumes one Google URL Inspection request and one unit of the server's shared inspection safety budget; this does not run live URL tests. |
@@ -87,6 +87,8 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-onl
87
87
  | **`sitemaps.submit`** / **`sitemaps.delete`** | Write | Submit or remove a sitemap. |
88
88
  | **`indexing.request`** | Write | Requests indexing through Google's Indexing API. Google currently restricts this API to pages containing `JobPosting` structured data or livestream pages containing `BroadcastEvent` inside `VideoObject`. It is not available for general webpage submission. |
89
89
 
90
+ > **Platform properties.** Search Console now supports platform properties for social/video accounts such as Instagram, TikTok, X, and YouTube in its UI. Google's current Search Console API documentation still defines `siteUrl` using URL-prefix and `sc-domain:` website-property forms and does not publish a platform-property API identifier contract. `sites.list` therefore preserves every identifier Google returns and marks whether it matches the documented API grammar; the other tools deliberately reject undocumented forms instead of inventing an identifier. Use the Search Console UI/export for a platform property until Google documents API support.
91
+
90
92
  Read-write mode requests the Google Search Console read-write and Indexing API scopes. Read-only mode requests only `https://www.googleapis.com/auth/webmasters.readonly` (plus `openid` and `email`) and does not register the five write tools. Read-write remains the default so existing deployments retain their current behavior; see [SETUP.md](SETUP.md) to select a mode before connecting users.
91
93
 
92
94
  Large analytics responses are bounded at the MCP boundary instead of being generated and then discarded by clients with structured-content limits. List-style tools expose `start_row` / `next_start_row` where applicable; ranked analytical tools expose `limit`, `start_row`, and `result_page` metadata including `has_more`, `truncated`, and `byte_limit_reached`. Continue paging while `has_more` is true rather than assuming one response is complete.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digestseo/mcp-gsc",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "private": false,
5
5
  "main": "scripts/glama-start.mjs",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ import { resolveIndexedPagesDateRange } from './indexed-pages-range';
58
58
  import { createQuickWinsInputSchema } from './quick-wins-schema';
59
59
  import { SITEMAP_URL_SCHEMA } from './sitemap-url-schema';
60
60
  import {
61
+ classifySearchConsolePropertyIdentifier,
61
62
  SEARCH_CONSOLE_PROPERTY_DESCRIPTION,
62
63
  SEARCH_CONSOLE_PROPERTY_SCHEMA,
63
64
  } from './search-console-property-schema';
@@ -132,6 +133,16 @@ const SITE_OUTPUT_SCHEMA = {
132
133
  permissionLevel: z.string(),
133
134
  };
134
135
 
136
+ const LISTED_SITE_OUTPUT_SCHEMA = {
137
+ ...SITE_OUTPUT_SCHEMA,
138
+ api_identifier_kind: z.enum(['domain', 'url_prefix', 'undocumented']),
139
+ mcp_site_url_accepted: z.boolean(),
140
+ unsupported_reason: z.string().optional(),
141
+ };
142
+
143
+ const PLATFORM_PROPERTY_API_NOTE =
144
+ "Google Search Console now has platform properties for social/video accounts in its UI, but the current Search Console API documentation still defines siteUrl using URL-prefix and sc-domain website-property forms. mcp-gsc preserves any other identifier returned by sites.list but will not pass an undocumented property identifier to API tools until Google publishes that contract.";
145
+
135
146
  const SITEMAP_OUTPUT_SCHEMA = z
136
147
  .object({
137
148
  path: z.string(),
@@ -168,7 +179,7 @@ const CAPABILITIES_OUTPUT_SCHEMA = {
168
179
  };
169
180
 
170
181
  const SITES_OUTPUT_SCHEMA = {
171
- sites: z.array(z.object(SITE_OUTPUT_SCHEMA)),
182
+ sites: z.array(z.object(LISTED_SITE_OUTPUT_SCHEMA)),
172
183
  };
173
184
 
174
185
  const SITE_DETAIL_OUTPUT_SCHEMA = {
@@ -205,6 +216,10 @@ const SEARCH_ANALYTICS_OUTPUT_SCHEMA = {
205
216
  row_count: z.number().int().nonnegative(),
206
217
  start_row: z.number().int().nonnegative(),
207
218
  rows: z.array(z.object(SEARCH_ROW_OUTPUT_SCHEMA)),
219
+ position_supported: z.boolean().describe(
220
+ 'Whether average position is supported for the selected Search Analytics search type. False for Google Discover.',
221
+ ),
222
+ position_note: z.string().optional(),
208
223
  provider_exhaustiveness_guaranteed: z.literal(false).describe(
209
224
  'False because the Search Analytics API does not guarantee every data row; Google can return only top rows even after local pagination is exhausted.',
210
225
  ),
@@ -516,7 +531,7 @@ const TOOL_CATALOG = [
516
531
  {
517
532
  name: 'sites.list',
518
533
  description:
519
- 'List the Google Search Console properties (sites) the connected Google account can access.',
534
+ 'List the Google Search Console properties (sites) the connected Google account can access, including whether each returned identifier matches the website-property siteUrl forms currently documented by the Search Console API.',
520
535
  },
521
536
  {
522
537
  name: 'sites.get',
@@ -710,7 +725,7 @@ class GscMcpRuntime {
710
725
  {
711
726
  title: 'List Search Console properties',
712
727
  description:
713
- "List the Google Search Console properties (sites) the connected Google account has access to. Returns an array of { siteUrl, permissionLevel }. Call this when the user asks 'what sites do I have?' or 'what properties are connected?', or when the user asks about SEO for a site and hasn't specified which property. Also useful as a discovery step before calling other tools that require a site_url argument.",
728
+ "List the Google Search Console properties (sites) the connected Google account has access to. Each entry preserves Google's siteUrl and permissionLevel and adds api_identifier_kind plus mcp_site_url_accepted. Google now exposes platform properties for social/video accounts in the Search Console UI, but its current API documentation still defines siteUrl using URL-prefix and sc-domain website-property forms; identifiers outside those documented forms are preserved here but marked unsupported rather than guessed. Call this when the user asks 'what sites do I have?' or 'what properties are connected?', or before another tool that requires site_url.",
714
729
  inputSchema: {},
715
730
  outputSchema: SITES_OUTPUT_SCHEMA,
716
731
  annotations: READ_ONLY_ANNOTATIONS,
@@ -718,7 +733,18 @@ class GscMcpRuntime {
718
733
  async () => {
719
734
  const googleId = this.requireGoogleId();
720
735
  const accessToken = await this.getAccessToken(googleId);
721
- const sites = await listSites(accessToken);
736
+ const sites = (await listSites(accessToken)).map((site) => {
737
+ const apiIdentifierKind = classifySearchConsolePropertyIdentifier(site.siteUrl);
738
+ const mcpSiteUrlAccepted = apiIdentifierKind !== 'undocumented';
739
+ return {
740
+ ...site,
741
+ api_identifier_kind: apiIdentifierKind,
742
+ mcp_site_url_accepted: mcpSiteUrlAccepted,
743
+ ...(!mcpSiteUrlAccepted
744
+ ? { unsupported_reason: PLATFORM_PROPERTY_API_NOTE }
745
+ : {}),
746
+ };
747
+ });
722
748
  return toolResponse(JSON.stringify(sites, null, 2), { sites });
723
749
  },
724
750
  );
@@ -1079,6 +1105,13 @@ class GscMcpRuntime {
1079
1105
  row_count: bounded.items.length,
1080
1106
  start_row,
1081
1107
  rows: bounded.items,
1108
+ position_supported: search_type !== 'discover',
1109
+ ...(search_type === 'discover'
1110
+ ? {
1111
+ position_note:
1112
+ "Google Discover does not support average position. Do not interpret rows[].position for search_type='discover'.",
1113
+ }
1114
+ : {}),
1082
1115
  provider_exhaustiveness_guaranteed: false,
1083
1116
  provider_note: SEARCH_ANALYTICS_PROVIDER_NOTE,
1084
1117
  has_more: bounded.resultPage.has_more,
@@ -1,7 +1,12 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  export const SEARCH_CONSOLE_PROPERTY_DESCRIPTION =
4
- "The Search Console property identifier, exactly as returned by sites.list. Domain properties use 'sc-domain:example.com'; URL-prefix properties use an HTTP or HTTPS URL, typically including the trailing slash, e.g. 'https://www.example.com/'. Passing the wrong property form can return a permission error even when the user owns the site - call sites.list first if unsure.";
4
+ "The Search Console property identifier, exactly as returned by sites.list. The current Search Console API documents Domain properties as 'sc-domain:example.com' and URL-prefix properties as an HTTP or HTTPS URL, typically including the trailing slash, e.g. 'https://www.example.com/'. Search Console platform properties for social/video accounts are available in the Search Console UI, but Google has not documented an API siteUrl identifier contract for them; mcp-gsc therefore does not guess one. Call sites.list first if unsure.";
5
+
6
+ export type SearchConsolePropertyIdentifierKind =
7
+ | 'domain'
8
+ | 'url_prefix'
9
+ | 'undocumented';
5
10
 
6
11
  function isDomainPropertyIdentifier(value: string): boolean {
7
12
  if (!value.startsWith('sc-domain:')) return false;
@@ -36,10 +41,18 @@ export function isSearchConsolePropertyIdentifier(value: string): boolean {
36
41
  return isDomainPropertyIdentifier(value) || isUrlPrefixPropertyIdentifier(value);
37
42
  }
38
43
 
44
+ export function classifySearchConsolePropertyIdentifier(
45
+ value: string,
46
+ ): SearchConsolePropertyIdentifierKind {
47
+ if (isDomainPropertyIdentifier(value)) return 'domain';
48
+ if (isUrlPrefixPropertyIdentifier(value)) return 'url_prefix';
49
+ return 'undocumented';
50
+ }
51
+
39
52
  export const SEARCH_CONSOLE_PROPERTY_SCHEMA = z
40
53
  .string()
41
54
  .refine(isSearchConsolePropertyIdentifier, {
42
55
  message:
43
- "Expected a Search Console property identifier such as 'sc-domain:example.com' or an HTTP/HTTPS URL-prefix property such as 'https://www.example.com/'.",
56
+ "Expected a Search Console API property identifier such as 'sc-domain:example.com' or an HTTP/HTTPS URL-prefix property such as 'https://www.example.com/'. Platform-property identifiers are not accepted until Google documents their API siteUrl contract.",
44
57
  })
45
58
  .describe(SEARCH_CONSOLE_PROPERTY_DESCRIPTION);