@digestseo/mcp-gsc 0.4.28 → 0.4.30

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
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.30] - 2026-09-23
11
+
12
+ ### Changed
13
+ - Search Analytics filters now default an omitted operator to Google's documented `equals` behavior in both `analytics.query` and `analytics.compare`, while both tools enforce the provider's 4096-character filter-expression ceiling through one shared schema.
14
+ - URL Inspection language codes are now validated as BCP-47 language tags before `urls.inspect` or `urls.inspect_many` can reserve local inspection budget, resolve Google credentials, or call the provider.
15
+
16
+ ## [0.4.29] - 2026-09-23
17
+
18
+ ### Fixed
19
+ - `urls.inspect_many` now rejects exact duplicate URLs before reserving local safety-budget units or calling Google, preventing one batch from wasting URL Inspection quota on repeated identical requests.
20
+ - `analytics.query` now rejects explicit `byPage` aggregation when the request also groups or filters by page, returning deterministic local guidance to use `auto` instead of forwarding a provider-invalid aggregation shape to Google.
21
+
10
22
  ## [0.4.28] - 2026-09-23
11
23
 
12
24
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digestseo/mcp-gsc",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "private": false,
5
5
  "main": "scripts/glama-start.mjs",
6
6
  "bin": {
package/src/google.ts CHANGED
@@ -371,6 +371,12 @@ export function assertSearchAnalyticsQueryCompatible(body: SearchAnalyticsQuery)
371
371
  }
372
372
  }
373
373
 
374
+ if (body.aggregationType === 'byPage' && hasPageGroupingOrFilter) {
375
+ throw new Error(
376
+ 'Search Analytics aggregationType byPage cannot be combined with page grouping or filtering; use auto instead.',
377
+ );
378
+ }
379
+
374
380
  if (body.aggregationType === 'byProperty') {
375
381
  if (hasPageGroupingOrFilter) {
376
382
  throw new Error(
package/src/index.ts CHANGED
@@ -66,6 +66,7 @@ import { CONTENT_DECAY_COMPARE_DAYS_SCHEMA } from './content-decay-schema';
66
66
  import { resolveIndexedPagesDateRange } from './indexed-pages-range';
67
67
  import { createQuickWinsInputSchema } from './quick-wins-schema';
68
68
  import { SITEMAP_URL_SCHEMA } from './sitemap-url-schema';
69
+ import { URL_INSPECTION_LANGUAGE_CODE_SCHEMA } from './url-inspection-language-schema';
69
70
  import {
70
71
  classifySearchConsolePropertyIdentifier,
71
72
  SEARCH_CONSOLE_PROPERTY_DESCRIPTION,
@@ -148,6 +149,32 @@ const SEARCH_ROW_OUTPUT_SCHEMA = {
148
149
  ),
149
150
  };
150
151
 
152
+ const SEARCH_ANALYTICS_FILTER_SCHEMA = z.object({
153
+ dimension: z.enum([
154
+ 'query',
155
+ 'page',
156
+ 'country',
157
+ 'device',
158
+ 'searchAppearance',
159
+ ]),
160
+ operator: z
161
+ .enum([
162
+ 'equals',
163
+ 'notEquals',
164
+ 'contains',
165
+ 'notContains',
166
+ 'includingRegex',
167
+ 'excludingRegex',
168
+ ])
169
+ .default('equals'),
170
+ expression: z.string().max(4096),
171
+ });
172
+
173
+ const SEARCH_ANALYTICS_FILTER_GROUP_SCHEMA = z.object({
174
+ groupType: z.literal('and').default('and'),
175
+ filters: z.array(SEARCH_ANALYTICS_FILTER_SCHEMA),
176
+ });
177
+
151
178
  const SITE_OUTPUT_SCHEMA = {
152
179
  siteUrl: z.string(),
153
180
  permissionLevel: z.string(),
@@ -943,12 +970,7 @@ class GscMcpRuntime {
943
970
  .describe(
944
971
  'The fully-qualified URL to inspect. Must belong to the site_url property: same domain for sc-domain properties, same URL prefix for URL-prefix properties.',
945
972
  ),
946
- language_code: z
947
- .string()
948
- .default('en-US')
949
- .describe(
950
- "BCP-47 language code for translatable strings in the result, e.g. 'en-US' or 'de-DE'.",
951
- ),
973
+ language_code: URL_INSPECTION_LANGUAGE_CODE_SCHEMA,
952
974
  },
953
975
  outputSchema: INSPECTION_OUTPUT_SCHEMA,
954
976
  annotations: READ_ONLY_ANNOTATIONS,
@@ -987,15 +1009,13 @@ class GscMcpRuntime {
987
1009
  .array(z.string().url())
988
1010
  .min(1)
989
1011
  .max(10)
1012
+ .refine((urls) => new Set(urls).size === urls.length, {
1013
+ message: 'inspection_urls must not contain duplicate URLs.',
1014
+ })
990
1015
  .describe(
991
- 'Between 1 and 10 fully-qualified URLs to inspect. Each URL must belong to the site_url property.',
992
- ),
993
- language_code: z
994
- .string()
995
- .default('en-US')
996
- .describe(
997
- "BCP-47 language code for translatable strings in the results, e.g. 'en-US' or 'de-DE'.",
1016
+ 'Between 1 and 10 unique fully-qualified URLs to inspect. Each URL must belong to the site_url property.',
998
1017
  ),
1018
+ language_code: URL_INSPECTION_LANGUAGE_CODE_SCHEMA,
999
1019
  },
1000
1020
  outputSchema: INSPECTION_BATCH_OUTPUT_SCHEMA,
1001
1021
  annotations: READ_ONLY_ANNOTATIONS,
@@ -1216,37 +1236,13 @@ class GscMcpRuntime {
1216
1236
  .enum(['auto', 'byNewsShowcasePanel', 'byPage', 'byProperty'])
1217
1237
  .default('auto')
1218
1238
  .describe(
1219
- "How Google aggregates metrics. Leave as 'auto' unless specific semantics are needed. 'byProperty' cannot be used with page grouping/filtering or search_type discover/googleNews. 'byNewsShowcasePanel' requires search_type discover/googleNews plus a searchAppearance equals NEWS_SHOWCASE filter, and cannot be combined with page grouping/filtering or another searchAppearance filter.",
1239
+ "How Google aggregates metrics. Leave as 'auto' unless specific semantics are needed. When grouping/filtering by page, use 'auto'; explicit 'byPage' and 'byProperty' are rejected for that request shape. 'byProperty' is also unavailable for search_type discover/googleNews. 'byNewsShowcasePanel' requires search_type discover/googleNews plus a searchAppearance equals NEWS_SHOWCASE filter, and cannot be combined with page grouping/filtering or another searchAppearance filter.",
1220
1240
  ),
1221
1241
  dimension_filter_groups: z
1222
- .array(
1223
- z.object({
1224
- groupType: z.literal('and').default('and'),
1225
- filters: z.array(
1226
- z.object({
1227
- dimension: z.enum([
1228
- 'query',
1229
- 'page',
1230
- 'country',
1231
- 'device',
1232
- 'searchAppearance',
1233
- ]),
1234
- operator: z.enum([
1235
- 'equals',
1236
- 'notEquals',
1237
- 'contains',
1238
- 'notContains',
1239
- 'includingRegex',
1240
- 'excludingRegex',
1241
- ]),
1242
- expression: z.string().max(4096),
1243
- }),
1244
- ),
1245
- }),
1246
- )
1242
+ .array(SEARCH_ANALYTICS_FILTER_GROUP_SCHEMA)
1247
1243
  .optional()
1248
1244
  .describe(
1249
- "Optional filters ANDed together, e.g. [{ groupType: 'and', filters: [{ dimension: 'country', operator: 'equals', expression: 'usa' }] }]. Countries use ISO 3166-1 alpha-3 codes. Query filters are not supported when search_type is discover or googleNews.",
1245
+ "Optional filters ANDed together, e.g. [{ groupType: 'and', filters: [{ dimension: 'country', expression: 'usa' }] }]. Filter operator defaults to 'equals' when omitted, matching Search Console. Expressions are limited to 4096 characters. Countries use ISO 3166-1 alpha-3 codes. Query filters are not supported when search_type is discover or googleNews.",
1250
1246
  ),
1251
1247
  },
1252
1248
  outputSchema: SEARCH_ANALYTICS_OUTPUT_SCHEMA,
@@ -2018,34 +2014,10 @@ class GscMcpRuntime {
2018
2014
  .default('web')
2019
2015
  .describe('Which Search Console search index to compare. The same search type is used for both periods. Discover and Google News are excluded because this tool returns average-position comparisons.'),
2020
2016
  dimension_filter_groups: z
2021
- .array(
2022
- z.object({
2023
- groupType: z.literal('and').default('and'),
2024
- filters: z.array(
2025
- z.object({
2026
- dimension: z.enum([
2027
- 'query',
2028
- 'page',
2029
- 'country',
2030
- 'device',
2031
- 'searchAppearance',
2032
- ]),
2033
- operator: z.enum([
2034
- 'equals',
2035
- 'notEquals',
2036
- 'contains',
2037
- 'notContains',
2038
- 'includingRegex',
2039
- 'excludingRegex',
2040
- ]),
2041
- expression: z.string(),
2042
- }),
2043
- ),
2044
- }),
2045
- )
2017
+ .array(SEARCH_ANALYTICS_FILTER_GROUP_SCHEMA)
2046
2018
  .optional()
2047
2019
  .describe(
2048
- "Optional Search Console filters applied identically to both periods. A caller-supplied query regex can approximate a manual brand/non-brand split, but it is not equivalent to Search Console's AI-assisted native Branded/Non-branded filter, which the Search Analytics API does not expose. Countries use ISO 3166-1 alpha-3 codes.",
2020
+ "Optional Search Console filters applied identically to both periods. Filter operator defaults to 'equals' when omitted, and expressions are limited to 4096 characters. A caller-supplied query regex can approximate a manual brand/non-brand split, but it is not equivalent to Search Console's AI-assisted native Branded/Non-branded filter, which the Search Analytics API does not expose. Countries use ISO 3166-1 alpha-3 codes.",
2049
2021
  ),
2050
2022
  limit: ANALYSIS_RESULT_LIMIT_SCHEMA,
2051
2023
  start_row: RESULT_START_ROW_SCHEMA,
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+
3
+ export const URL_INSPECTION_LANGUAGE_CODE_SCHEMA = z
4
+ .string()
5
+ .superRefine((value, ctx) => {
6
+ try {
7
+ if (Intl.getCanonicalLocales(value).length !== 1) {
8
+ throw new RangeError('Invalid language tag');
9
+ }
10
+ } catch {
11
+ ctx.addIssue({
12
+ code: 'custom',
13
+ message:
14
+ "language_code must be a valid BCP-47 language tag, e.g. 'en-US' or 'de-CH'.",
15
+ });
16
+ }
17
+ })
18
+ .default('en-US')
19
+ .describe(
20
+ "BCP-47 language code for translatable strings in the result, e.g. 'en-US' or 'de-CH'.",
21
+ );