@digestseo/mcp-gsc 0.4.35 → 0.4.36
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 +8 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/date-validation.ts +6 -2
- package/src/index.ts +48 -4
- package/src/indexed-pages-range.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.36] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `analytics.query` now exposes an explicit `multimodal_report_isolatable: false` boundary and matching runtime guidance because Search Console's new web multimodal filter is currently UI-only; the documented Search Analytics API still exposes only web/image/video/news/discover/googleNews search types.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- User-supplied Search Analytics ranges now reject future end dates against Search Console's Pacific calendar before reserving local safety budget, resolving Google credentials, or calling the provider; the same boundary now applies to explicit indexed-page ranges and weekly-digest input.
|
|
17
|
+
|
|
10
18
|
## [0.4.35] - 2026-09-24
|
|
11
19
|
|
|
12
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 23 tools. Read-onl
|
|
|
108
108
|
| **`server.capabilities`** | Read | List every tool this server exposes and report local Google credential state. `connected` means a stored refresh credential is present; the response also exposes `auth_status_basis` and `provider_auth_live_verified: false` because this discovery call does not probe Google. A later Google tool call can still require reconnection if the credential expired or was revoked. Takes no arguments. |
|
|
109
109
|
| **`sites.list`** | Read | List the Search Console properties the connected Google account can access (`siteUrl`, `permissionLevel`). |
|
|
110
110
|
| **`sites.get`** | Read | Retrieve one exact Search Console property and the connected account's permission level for it. |
|
|
111
|
-
| **`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. `data_state: "all"` can include fresh preliminary data, while `final` returns only finalized data; responses echo `data_state` plus `preliminary_data_possible`, and date/hour groupings preserve Google's incomplete-data boundary metadata when returned. Search appearance discovery is a two-step workflow: group by `searchAppearance` alone to discover the exact values Google returns for the property, then filter to one returned value in a separate query when grouping by page/query/country/device/date/hour. For `search_type: "discover"` or `"googleNews"`, `position_supported` is `false`; those reports expose clicks, impressions, and CTR but not average position, so returned rows can omit `position`, and query grouping/filtering is unavailable. Search Console's dedicated Generative AI performance reports are not exposed by the current documented Search Analytics API as a dedicated search type or filter selector; `generative_ai_report_isolatable` is therefore `false`. |
|
|
111
|
+
| **`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. `data_state: "all"` can include fresh preliminary data, while `final` returns only finalized data; responses echo `data_state` plus `preliminary_data_possible`, and date/hour groupings preserve Google's incomplete-data boundary metadata when returned. Search appearance discovery is a two-step workflow: group by `searchAppearance` alone to discover the exact values Google returns for the property, then filter to one returned value in a separate query when grouping by page/query/country/device/date/hour. For `search_type: "discover"` or `"googleNews"`, `position_supported` is `false`; those reports expose clicks, impressions, and CTR but not average position, so returned rows can omit `position`, and query grouping/filtering is unavailable. Search Console's dedicated Generative AI performance reports are not exposed by the current documented Search Analytics API as a dedicated search type or filter selector; `generative_ai_report_isolatable` is therefore `false`. Search Console's new web multimodal UI filter is likewise not exposed as a documented Search Analytics API selector; `multimodal_report_isolatable` is therefore `false`. |
|
|
112
112
|
| **`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. These query-centric helpers support web, image, video, and the Search results News tab; Discover and the Google News report are excluded because they do not expose query data. |
|
|
113
113
|
| **`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. |
|
|
114
114
|
| **`urls.inspect_many`** | Read | Inspect Google's indexed versions of up to 10 URLs with bounded concurrency of 3 in one call while preserving input-order results. 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. |
|
|
@@ -149,6 +149,8 @@ Query regexes are a manual approximation only. Search Console's native Branded/N
|
|
|
149
149
|
|
|
150
150
|
Search Console also has dedicated Generative AI performance reports for Search and Discover. The current documented Search Analytics API does not expose a dedicated Generative AI search type or filter selector, so this MCP does not guess one. AI Overviews and AI Mode remain included in overall web Search performance data; use the Search Console UI for the dedicated Generative AI report until Google documents API access.
|
|
151
151
|
|
|
152
|
+
As of September 24, 2026, Search Console also has a web multimodal performance filter in the UI for searches such as Lens, Circle to Search, image uploads, and Chrome image search. The current Search Analytics API and Discovery document still expose only `web`, `image`, `video`, `news`, `discover`, and `googleNews` search types, so this MCP does not invent `search_type: "multimodal"` or guess a `searchAppearance` value. Use the Search Console UI for isolated multimodal reporting until Google documents API access.
|
|
153
|
+
|
|
152
154
|
## Connect in your AI client
|
|
153
155
|
|
|
154
156
|
Once you've deployed the server (see **[SETUP.md](SETUP.md)**), connect it by pasting your Worker's `/mcp` URL into your client:
|
package/package.json
CHANGED
package/src/date-validation.ts
CHANGED
|
@@ -81,10 +81,14 @@ export function assertDateRange(
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function assertDateNotInFuture(
|
|
84
|
+
export function assertDateNotInFuture(
|
|
85
|
+
date: string,
|
|
86
|
+
today: string,
|
|
87
|
+
dateName = 'End date',
|
|
88
|
+
): void {
|
|
85
89
|
if (date > today) {
|
|
86
90
|
throw new Error(
|
|
87
|
-
|
|
91
|
+
`${dateName} must be today or earlier (current Search Console Pacific date: ${today}). Google Search Console has no data for dates that have not happened yet.`,
|
|
88
92
|
);
|
|
89
93
|
}
|
|
90
94
|
}
|
package/src/index.ts
CHANGED
|
@@ -402,6 +402,10 @@ const SEARCH_ANALYTICS_OUTPUT_SCHEMA = {
|
|
|
402
402
|
'False because the current documented Search Analytics API exposes no dedicated Generative AI report search type or filter selector.',
|
|
403
403
|
),
|
|
404
404
|
generative_ai_note: z.string(),
|
|
405
|
+
multimodal_report_isolatable: z.literal(false).describe(
|
|
406
|
+
'False because the current documented Search Analytics API exposes no dedicated web multimodal search type or filter selector.',
|
|
407
|
+
),
|
|
408
|
+
multimodal_note: z.string(),
|
|
405
409
|
next_start_row: z.number().int().nonnegative().optional(),
|
|
406
410
|
has_more: z.boolean(),
|
|
407
411
|
truncated: z.boolean(),
|
|
@@ -698,6 +702,9 @@ const SEARCH_ANALYTICS_PROVIDER_NOTE =
|
|
|
698
702
|
const GENERATIVE_AI_REPORT_API_NOTE =
|
|
699
703
|
"Search Console's dedicated Generative AI performance reports are not exposed by the current documented Search Analytics API as a dedicated search type or filter selector. AI Overviews and AI Mode remain included in overall web Search performance data. Do not infer isolated Generative AI metrics from analytics.query or guess a searchAppearance identifier.";
|
|
700
704
|
|
|
705
|
+
const MULTIMODAL_REPORT_API_NOTE =
|
|
706
|
+
"Search Console's web multimodal performance filter is currently available in the Search Console UI, but the documented Search Analytics API does not expose a multimodal search type or filter selector. Current API search types remain web, image, video, news, discover, and googleNews. Do not pass search_type='multimodal' or guess a searchAppearance identifier to isolate Lens, Circle to Search, image-upload, or Chrome image-search traffic.";
|
|
707
|
+
|
|
701
708
|
function paginationMetadata(result: PaginatedSearchAnalyticsResult) {
|
|
702
709
|
return {
|
|
703
710
|
rows_fetched: result.rows.length,
|
|
@@ -1279,6 +1286,13 @@ class GscMcpRuntime {
|
|
|
1279
1286
|
' included in overall web Search performance data. Do not guess a',
|
|
1280
1287
|
' searchAppearance identifier or claim analytics.query isolates the',
|
|
1281
1288
|
' dedicated Generative AI report.',
|
|
1289
|
+
'- WEB MULTIMODAL REPORT: Search Console now has a web multimodal',
|
|
1290
|
+
' performance filter in its UI for searches such as Lens, Circle to',
|
|
1291
|
+
' Search, image uploads, and Chrome image search. The current',
|
|
1292
|
+
' documented Search Analytics API still exposes only web, image,',
|
|
1293
|
+
' video, news, discover, and googleNews search types, with no',
|
|
1294
|
+
' multimodal selector. Do not pass search_type="multimodal" or guess',
|
|
1295
|
+
' a searchAppearance identifier to isolate this UI report.',
|
|
1282
1296
|
'- Use dimension_filter_groups to filter by country, device, query',
|
|
1283
1297
|
' content, page URL, or search feature. includingRegex and',
|
|
1284
1298
|
' excludingRegex use RE2 syntax. A query regex can provide a manual',
|
|
@@ -1358,7 +1372,7 @@ class GscMcpRuntime {
|
|
|
1358
1372
|
search_type: z
|
|
1359
1373
|
.enum(['web', 'image', 'video', 'news', 'discover', 'googleNews'])
|
|
1360
1374
|
.default('web')
|
|
1361
|
-
.describe('Which documented Search Analytics search index to query. Defaults to web. Google Discover and Google News do not support query grouping/filtering or average position. The current API does not expose a dedicated Generative AI performance-report search type.'),
|
|
1375
|
+
.describe('Which documented Search Analytics search index to query. Defaults to web. Google Discover and Google News do not support query grouping/filtering or average position. The current API does not expose a dedicated Generative AI performance-report search type or the Search Console UI web multimodal filter as a search type.'),
|
|
1362
1376
|
aggregation_type: z
|
|
1363
1377
|
.enum(['auto', 'byNewsShowcasePanel', 'byPage', 'byProperty'])
|
|
1364
1378
|
.default('auto')
|
|
@@ -1388,6 +1402,11 @@ class GscMcpRuntime {
|
|
|
1388
1402
|
dimension_filter_groups,
|
|
1389
1403
|
}) => {
|
|
1390
1404
|
assertDateRange(start_date, end_date);
|
|
1405
|
+
assertDateNotInFuture(
|
|
1406
|
+
end_date,
|
|
1407
|
+
getSearchConsoleCalendarDate(),
|
|
1408
|
+
'end_date',
|
|
1409
|
+
);
|
|
1391
1410
|
const sourceRowLimit = Math.min(row_limit, MAX_DIRECT_SOURCE_ROWS);
|
|
1392
1411
|
const query = {
|
|
1393
1412
|
startDate: start_date,
|
|
@@ -1439,6 +1458,8 @@ class GscMcpRuntime {
|
|
|
1439
1458
|
provider_note: SEARCH_ANALYTICS_PROVIDER_NOTE,
|
|
1440
1459
|
generative_ai_report_isolatable: false,
|
|
1441
1460
|
generative_ai_note: GENERATIVE_AI_REPORT_API_NOTE,
|
|
1461
|
+
multimodal_report_isolatable: false,
|
|
1462
|
+
multimodal_note: MULTIMODAL_REPORT_API_NOTE,
|
|
1442
1463
|
has_more: bounded.resultPage.has_more,
|
|
1443
1464
|
truncated: bounded.resultPage.truncated,
|
|
1444
1465
|
byte_limit_reached: bounded.resultPage.byte_limit_reached,
|
|
@@ -1497,6 +1518,11 @@ class GscMcpRuntime {
|
|
|
1497
1518
|
},
|
|
1498
1519
|
async ({ site_url, page_url, start_date, end_date, search_type, row_limit = 100, start_row = 0 }) => {
|
|
1499
1520
|
assertDateRange(start_date, end_date);
|
|
1521
|
+
assertDateNotInFuture(
|
|
1522
|
+
end_date,
|
|
1523
|
+
getSearchConsoleCalendarDate(),
|
|
1524
|
+
'end_date',
|
|
1525
|
+
);
|
|
1500
1526
|
assertUrlWithinSearchConsoleProperty(page_url, site_url, 'page_url');
|
|
1501
1527
|
const googleId = this.requireGoogleId();
|
|
1502
1528
|
const rateLimitError = await this.rateLimitError(googleId, 'insights.page_queries');
|
|
@@ -1582,6 +1608,11 @@ class GscMcpRuntime {
|
|
|
1582
1608
|
},
|
|
1583
1609
|
async ({ site_url, query, start_date, end_date, search_type, row_limit = 100, start_row = 0 }) => {
|
|
1584
1610
|
assertDateRange(start_date, end_date);
|
|
1611
|
+
assertDateNotInFuture(
|
|
1612
|
+
end_date,
|
|
1613
|
+
getSearchConsoleCalendarDate(),
|
|
1614
|
+
'end_date',
|
|
1615
|
+
);
|
|
1585
1616
|
const googleId = this.requireGoogleId();
|
|
1586
1617
|
const rateLimitError = await this.rateLimitError(googleId, 'insights.query_pages');
|
|
1587
1618
|
if (rateLimitError) return rateLimitError;
|
|
@@ -1776,6 +1807,11 @@ class GscMcpRuntime {
|
|
|
1776
1807
|
},
|
|
1777
1808
|
async ({ site_url, start_date, end_date, min_impressions, min_position, max_position, limit = DEFAULT_ANALYSIS_RESULT_LIMIT, start_row = 0 }) => {
|
|
1778
1809
|
assertDateRange(start_date, end_date);
|
|
1810
|
+
assertDateNotInFuture(
|
|
1811
|
+
end_date,
|
|
1812
|
+
getSearchConsoleCalendarDate(),
|
|
1813
|
+
'end_date',
|
|
1814
|
+
);
|
|
1779
1815
|
const googleId = this.requireGoogleId();
|
|
1780
1816
|
const rateLimitError = await this.rateLimitError(googleId, 'insights.quick_wins');
|
|
1781
1817
|
if (rateLimitError) return rateLimitError;
|
|
@@ -1823,6 +1859,11 @@ class GscMcpRuntime {
|
|
|
1823
1859
|
},
|
|
1824
1860
|
async ({ site_url, start_date, end_date, min_impressions, min_page_percentage, limit = DEFAULT_ANALYSIS_RESULT_LIMIT, start_row = 0 }) => {
|
|
1825
1861
|
assertDateRange(start_date, end_date);
|
|
1862
|
+
assertDateNotInFuture(
|
|
1863
|
+
end_date,
|
|
1864
|
+
getSearchConsoleCalendarDate(),
|
|
1865
|
+
'end_date',
|
|
1866
|
+
);
|
|
1826
1867
|
const googleId = this.requireGoogleId();
|
|
1827
1868
|
const rateLimitError = await this.rateLimitError(googleId, 'insights.cannibalization');
|
|
1828
1869
|
if (rateLimitError) return rateLimitError;
|
|
@@ -2187,6 +2228,9 @@ class GscMcpRuntime {
|
|
|
2187
2228
|
}) => {
|
|
2188
2229
|
assertDateRange(start_date_a, end_date_a, 'start_date_a', 'end_date_a');
|
|
2189
2230
|
assertDateRange(start_date_b, end_date_b, 'start_date_b', 'end_date_b');
|
|
2231
|
+
const today = getSearchConsoleCalendarDate();
|
|
2232
|
+
assertDateNotInFuture(end_date_a, today, 'end_date_a');
|
|
2233
|
+
assertDateNotInFuture(end_date_b, today, 'end_date_b');
|
|
2190
2234
|
const googleId = this.requireGoogleId();
|
|
2191
2235
|
const rateLimitError = await this.rateLimitError(googleId, 'analytics.compare');
|
|
2192
2236
|
if (rateLimitError) return rateLimitError;
|
|
@@ -2248,12 +2292,12 @@ class GscMcpRuntime {
|
|
|
2248
2292
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
2249
2293
|
},
|
|
2250
2294
|
async ({ site_url, end_date }) => {
|
|
2295
|
+
const today = getSearchConsoleCalendarDate();
|
|
2296
|
+
const resolvedEndDate = resolveWeeklyDigestEndDate(end_date, today);
|
|
2297
|
+
assertDateNotInFuture(resolvedEndDate, today, 'end_date');
|
|
2251
2298
|
const googleId = this.requireGoogleId();
|
|
2252
2299
|
const rateLimitError = await this.rateLimitError(googleId, 'reports.weekly_digest');
|
|
2253
2300
|
if (rateLimitError) return rateLimitError;
|
|
2254
|
-
const today = getSearchConsoleCalendarDate();
|
|
2255
|
-
const resolvedEndDate = resolveWeeklyDigestEndDate(end_date, today);
|
|
2256
|
-
assertDateNotInFuture(resolvedEndDate, today);
|
|
2257
2301
|
|
|
2258
2302
|
try {
|
|
2259
2303
|
const markdown = await generateWeeklyDigest(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertDateRange } from './date-validation';
|
|
1
|
+
import { assertDateNotInFuture, assertDateRange } from './date-validation';
|
|
2
2
|
|
|
3
3
|
export interface IndexedPagesDateRange {
|
|
4
4
|
startDate: string;
|
|
@@ -26,6 +26,7 @@ export function resolveIndexedPagesDateRange(
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
if (!startDate) {
|
|
29
|
+
assertDateNotInFuture(endDate!, today, 'end_date');
|
|
29
30
|
return {
|
|
30
31
|
startDate: addUtcDays(endDate!, -29),
|
|
31
32
|
endDate: endDate!,
|
|
@@ -50,5 +51,6 @@ export function resolveIndexedPagesDateRange(
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
assertDateRange(startDate, endDate);
|
|
54
|
+
assertDateNotInFuture(endDate, today, 'end_date');
|
|
53
55
|
return { startDate, endDate };
|
|
54
56
|
}
|