@digestseo/mcp-gsc 0.4.30 → 0.4.32
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 +18 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/google.ts +86 -0
- package/src/index.ts +144 -39
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.32] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `urls.inspect` and successful `urls.inspect_many` items now include a conservative, schema-discoverable `index_status_summary` derived from Google's documented index-status verdict and key crawl/indexing fields while preserving the complete raw provider response.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Successful `sitemaps.submit` and `sitemaps.delete` results now expose explicit Search Console effect boundaries so agents do not mistake submission for completed processing/indexing or Search Console removal for deletion of the hosted sitemap file/deindexing.
|
|
17
|
+
|
|
18
|
+
## [0.4.31] - 2026-09-24
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Gemini CLI can now install this repository as a native extension, bundling the verified local Streamable HTTP MCP connection with the existing `gsc-weekly-review`, `gsc-indexing-triage`, and `gsc-search-opportunities` Agent Skills.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Exact Search Analytics country and device filters now reject malformed provider values before reserving local Search Analytics budget, resolving Google credentials, or calling Search Console; substring and RE2 regex operators remain unrestricted.
|
|
25
|
+
- `sites.list` and `sites.get` now expose explicit `verification_state` and `owner` fields alongside Google's raw `permissionLevel`, so agents can distinguish unverified properties and owner-only Indexing API prerequisites before choosing a property workflow.
|
|
26
|
+
|
|
10
27
|
## [0.4.30] - 2026-09-23
|
|
11
28
|
|
|
12
29
|
### Changed
|
|
@@ -198,6 +215,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
198
215
|
## [0.4.2] - 2026-09-21
|
|
199
216
|
|
|
200
217
|
### Fixed
|
|
218
|
+
- Search Analytics now rejects malformed exact-match country/device filters during MCP input validation, before local rate-limit, credential, or Google API work, while preserving contains and regex filter semantics.
|
|
201
219
|
- Cannibalization totals and impression shares now expose their query/page-row aggregation scope explicitly so clients do not mistake summed page impressions for a true query-level Search Console total.
|
|
202
220
|
- Discover Search Analytics calls now reject unsupported query grouping/filtering before reaching Google, and query-centric/comparison tools no longer advertise Discover where their contracts depend on query or average-position data that Discover does not provide.
|
|
203
221
|
- Quick-win guidance now treats Search Console position as an aggregate average-position metric rather than a literal current rank, and metadata no longer implies CTR is an eligibility filter when the implementation does not filter on CTR.
|
package/README.md
CHANGED
|
@@ -75,6 +75,14 @@ copilot plugin install mcp-gsc@digestseo
|
|
|
75
75
|
|
|
76
76
|
The marketplace path makes the plugin discoverable and updateable through Copilot's marketplace workflow. Direct repository install also remains supported with `copilot plugin install AKzar1el/mcp-gsc`. The Copilot plugin bundles the same three Agent Skills plus the local loopback MCP configuration. Start the npm launcher before using the plugin's MCP server; the plugin does not start or deploy mcp-gsc for you.
|
|
77
77
|
|
|
78
|
+
**Gemini CLI**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
gemini extensions install https://github.com/AKzar1el/mcp-gsc
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The Gemini CLI extension bundles the verified local loopback MCP configuration plus the same three Agent Skills: `gsc-weekly-review`, `gsc-indexing-triage`, and `gsc-search-opportunities`. Start the npm launcher before using the extension; the extension does not start or deploy mcp-gsc for you.
|
|
85
|
+
|
|
78
86
|
**Kiro**
|
|
79
87
|
|
|
80
88
|
[](https://kiro.dev/launch/mcp/add?name=mcp-gsc&config=%7B%22url%22%3A%22http%3A%2F%2F127.0.0.1%3A8080%2Fmcp%22%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D)
|
package/package.json
CHANGED
package/src/google.ts
CHANGED
|
@@ -314,6 +314,27 @@ export interface DimensionFilter {
|
|
|
314
314
|
expression: string;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
export function assertSearchAnalyticsFilterCompatible(
|
|
318
|
+
filter: DimensionFilter,
|
|
319
|
+
): void {
|
|
320
|
+
if (filter.operator !== 'equals' && filter.operator !== 'notEquals') return;
|
|
321
|
+
|
|
322
|
+
if (filter.dimension === 'country' && !/^[A-Za-z]{3}$/.test(filter.expression)) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
'Search Analytics exact country filters must use a three-letter ISO 3166-1 alpha-3 code.',
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (
|
|
329
|
+
filter.dimension === 'device' &&
|
|
330
|
+
!['DESKTOP', 'MOBILE', 'TABLET'].includes(filter.expression.toUpperCase())
|
|
331
|
+
) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
'Search Analytics exact device filters must use DESKTOP, MOBILE, or TABLET.',
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
317
338
|
export interface DimensionFilterGroup {
|
|
318
339
|
groupType: 'and';
|
|
319
340
|
filters: DimensionFilter[];
|
|
@@ -369,6 +390,7 @@ export function assertSearchAnalyticsQueryCompatible(body: SearchAnalyticsQuery)
|
|
|
369
390
|
if (filter.expression.length > 4096) {
|
|
370
391
|
throw new Error('Search Analytics filter expressions must be at most 4096 characters.');
|
|
371
392
|
}
|
|
393
|
+
assertSearchAnalyticsFilterCompatible(filter);
|
|
372
394
|
}
|
|
373
395
|
|
|
374
396
|
if (body.aggregationType === 'byPage' && hasPageGroupingOrFilter) {
|
|
@@ -526,6 +548,70 @@ export async function inspectUrl(
|
|
|
526
548
|
return data.inspectionResult ?? null;
|
|
527
549
|
}
|
|
528
550
|
|
|
551
|
+
export type UrlInspectionIndexedState = 'indexed' | 'not_indexed' | 'unknown';
|
|
552
|
+
|
|
553
|
+
export interface UrlInspectionIndexStatusSummary {
|
|
554
|
+
indexed_state: UrlInspectionIndexedState;
|
|
555
|
+
verdict?: string;
|
|
556
|
+
coverage_state?: string;
|
|
557
|
+
robots_txt_state?: string;
|
|
558
|
+
indexing_state?: string;
|
|
559
|
+
last_crawl_time?: string;
|
|
560
|
+
page_fetch_state?: string;
|
|
561
|
+
google_canonical?: string;
|
|
562
|
+
user_canonical?: string;
|
|
563
|
+
crawled_as?: string;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function asUnknownRecord(value: unknown): Record<string, unknown> | undefined {
|
|
567
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
568
|
+
? (value as Record<string, unknown>)
|
|
569
|
+
: undefined;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function optionalStringField(
|
|
573
|
+
record: Record<string, unknown> | undefined,
|
|
574
|
+
key: string,
|
|
575
|
+
): string | undefined {
|
|
576
|
+
const value = record?.[key];
|
|
577
|
+
return typeof value === 'string' ? value : undefined;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function summarizeUrlInspectionIndexStatus(
|
|
581
|
+
inspectionResult: unknown,
|
|
582
|
+
): UrlInspectionIndexStatusSummary {
|
|
583
|
+
const result = asUnknownRecord(inspectionResult);
|
|
584
|
+
const indexStatus = asUnknownRecord(result?.indexStatusResult);
|
|
585
|
+
const verdict = optionalStringField(indexStatus, 'verdict');
|
|
586
|
+
const coverageState = optionalStringField(indexStatus, 'coverageState');
|
|
587
|
+
const robotsTxtState = optionalStringField(indexStatus, 'robotsTxtState');
|
|
588
|
+
const indexingState = optionalStringField(indexStatus, 'indexingState');
|
|
589
|
+
const lastCrawlTime = optionalStringField(indexStatus, 'lastCrawlTime');
|
|
590
|
+
const pageFetchState = optionalStringField(indexStatus, 'pageFetchState');
|
|
591
|
+
const googleCanonical = optionalStringField(indexStatus, 'googleCanonical');
|
|
592
|
+
const userCanonical = optionalStringField(indexStatus, 'userCanonical');
|
|
593
|
+
const crawledAs = optionalStringField(indexStatus, 'crawledAs');
|
|
594
|
+
const indexedState: UrlInspectionIndexedState =
|
|
595
|
+
verdict === 'PASS'
|
|
596
|
+
? 'indexed'
|
|
597
|
+
: verdict === 'FAIL' || verdict === 'NEUTRAL'
|
|
598
|
+
? 'not_indexed'
|
|
599
|
+
: 'unknown';
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
indexed_state: indexedState,
|
|
603
|
+
...(verdict !== undefined ? { verdict } : {}),
|
|
604
|
+
...(coverageState !== undefined ? { coverage_state: coverageState } : {}),
|
|
605
|
+
...(robotsTxtState !== undefined ? { robots_txt_state: robotsTxtState } : {}),
|
|
606
|
+
...(indexingState !== undefined ? { indexing_state: indexingState } : {}),
|
|
607
|
+
...(lastCrawlTime !== undefined ? { last_crawl_time: lastCrawlTime } : {}),
|
|
608
|
+
...(pageFetchState !== undefined ? { page_fetch_state: pageFetchState } : {}),
|
|
609
|
+
...(googleCanonical !== undefined ? { google_canonical: googleCanonical } : {}),
|
|
610
|
+
...(userCanonical !== undefined ? { user_canonical: userCanonical } : {}),
|
|
611
|
+
...(crawledAs !== undefined ? { crawled_as: crawledAs } : {}),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
529
615
|
export interface UrlInspectionBatchResult {
|
|
530
616
|
inspectionUrl: string;
|
|
531
617
|
inspectionResult?: unknown;
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
MCP_RECONNECT_INSTRUCTION,
|
|
14
14
|
inspectUrl,
|
|
15
15
|
inspectUrlsBoundedConcurrently,
|
|
16
|
+
summarizeUrlInspectionIndexStatus,
|
|
16
17
|
URL_INSPECTION_BATCH_CONCURRENCY,
|
|
17
18
|
listSitemaps,
|
|
18
19
|
listSites,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
requestIndexingRemoval,
|
|
33
34
|
getIndexingNotificationMetadata,
|
|
34
35
|
processPerformanceComparison,
|
|
36
|
+
assertSearchAnalyticsFilterCompatible,
|
|
35
37
|
type PaginatedSearchAnalyticsResult,
|
|
36
38
|
} from './google';
|
|
37
39
|
import {
|
|
@@ -149,35 +151,57 @@ const SEARCH_ROW_OUTPUT_SCHEMA = {
|
|
|
149
151
|
),
|
|
150
152
|
};
|
|
151
153
|
|
|
152
|
-
const SEARCH_ANALYTICS_FILTER_SCHEMA = z
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
154
|
+
const SEARCH_ANALYTICS_FILTER_SCHEMA = z
|
|
155
|
+
.object({
|
|
156
|
+
dimension: z.enum([
|
|
157
|
+
'query',
|
|
158
|
+
'page',
|
|
159
|
+
'country',
|
|
160
|
+
'device',
|
|
161
|
+
'searchAppearance',
|
|
162
|
+
]),
|
|
163
|
+
operator: z
|
|
164
|
+
.enum([
|
|
165
|
+
'equals',
|
|
166
|
+
'notEquals',
|
|
167
|
+
'contains',
|
|
168
|
+
'notContains',
|
|
169
|
+
'includingRegex',
|
|
170
|
+
'excludingRegex',
|
|
171
|
+
])
|
|
172
|
+
.default('equals'),
|
|
173
|
+
expression: z.string().max(4096),
|
|
174
|
+
})
|
|
175
|
+
.superRefine((filter, ctx) => {
|
|
176
|
+
try {
|
|
177
|
+
assertSearchAnalyticsFilterCompatible(filter);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
ctx.addIssue({
|
|
180
|
+
code: 'custom',
|
|
181
|
+
path: ['expression'],
|
|
182
|
+
message: (error as Error).message,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
172
186
|
|
|
173
187
|
const SEARCH_ANALYTICS_FILTER_GROUP_SCHEMA = z.object({
|
|
174
188
|
groupType: z.literal('and').default('and'),
|
|
175
189
|
filters: z.array(SEARCH_ANALYTICS_FILTER_SCHEMA),
|
|
176
190
|
});
|
|
177
191
|
|
|
192
|
+
const SITE_VERIFICATION_STATE_SCHEMA = z
|
|
193
|
+
.enum(['verified', 'unverified', 'unknown'])
|
|
194
|
+
.describe(
|
|
195
|
+
"Derived from Google's permissionLevel. siteOwner/siteFullUser/siteRestrictedUser are verified, siteUnverifiedUser is unverified, and any future undocumented permission level is reported as unknown.",
|
|
196
|
+
);
|
|
197
|
+
|
|
178
198
|
const SITE_OUTPUT_SCHEMA = {
|
|
179
199
|
siteUrl: z.string(),
|
|
180
200
|
permissionLevel: z.string(),
|
|
201
|
+
verification_state: SITE_VERIFICATION_STATE_SCHEMA,
|
|
202
|
+
owner: z.boolean().describe(
|
|
203
|
+
"True only when Google returns permissionLevel 'siteOwner'. Restricted Indexing API workflows require owner-level Search Console access.",
|
|
204
|
+
),
|
|
181
205
|
};
|
|
182
206
|
|
|
183
207
|
const LISTED_SITE_OUTPUT_SCHEMA = {
|
|
@@ -190,6 +214,26 @@ const LISTED_SITE_OUTPUT_SCHEMA = {
|
|
|
190
214
|
const PLATFORM_PROPERTY_API_NOTE =
|
|
191
215
|
"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.";
|
|
192
216
|
|
|
217
|
+
function sitePermissionState(permissionLevel: string): {
|
|
218
|
+
verification_state: 'verified' | 'unverified' | 'unknown';
|
|
219
|
+
owner: boolean;
|
|
220
|
+
} {
|
|
221
|
+
if (permissionLevel === 'siteUnverifiedUser') {
|
|
222
|
+
return { verification_state: 'unverified', owner: false };
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
permissionLevel === 'siteOwner' ||
|
|
226
|
+
permissionLevel === 'siteFullUser' ||
|
|
227
|
+
permissionLevel === 'siteRestrictedUser'
|
|
228
|
+
) {
|
|
229
|
+
return {
|
|
230
|
+
verification_state: 'verified',
|
|
231
|
+
owner: permissionLevel === 'siteOwner',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { verification_state: 'unknown', owner: false };
|
|
235
|
+
}
|
|
236
|
+
|
|
193
237
|
const SITEMAP_PROVIDER_NOTE =
|
|
194
238
|
"Google's lastSubmitted is the time the sitemap was submitted to Search Console; it is not the sitemap file's generation, deployment, or last-modified time. lastDownloaded is the time Google last downloaded the sitemap; it is not a page crawl or indexing timestamp.";
|
|
195
239
|
|
|
@@ -276,8 +320,24 @@ const INDEXING_PROVIDER_USAGE_NOTE =
|
|
|
276
320
|
const URL_INSPECTION_SCOPE_NOTE =
|
|
277
321
|
"Google's URL Inspection API reports the version currently known in Google's index; it does not run a live URL test or prove current live-page indexability. The mobileUsabilityResult field is deprecated and may be absent.";
|
|
278
322
|
|
|
323
|
+
const URL_INSPECTION_INDEX_STATUS_SUMMARY_SCHEMA = z.object({
|
|
324
|
+
indexed_state: z.enum(['indexed', 'not_indexed', 'unknown']).describe(
|
|
325
|
+
"Conservative state derived only from Google's indexStatusResult.verdict: PASS => indexed, FAIL/NEUTRAL => not_indexed, and missing/unknown/reserved verdicts => unknown.",
|
|
326
|
+
),
|
|
327
|
+
verdict: z.string().optional(),
|
|
328
|
+
coverage_state: z.string().optional(),
|
|
329
|
+
robots_txt_state: z.string().optional(),
|
|
330
|
+
indexing_state: z.string().optional(),
|
|
331
|
+
last_crawl_time: z.string().optional(),
|
|
332
|
+
page_fetch_state: z.string().optional(),
|
|
333
|
+
google_canonical: z.string().optional(),
|
|
334
|
+
user_canonical: z.string().optional(),
|
|
335
|
+
crawled_as: z.string().optional(),
|
|
336
|
+
});
|
|
337
|
+
|
|
279
338
|
const INSPECTION_OUTPUT_SCHEMA = {
|
|
280
339
|
inspection_result: z.unknown(),
|
|
340
|
+
index_status_summary: URL_INSPECTION_INDEX_STATUS_SUMMARY_SCHEMA,
|
|
281
341
|
note: z.string(),
|
|
282
342
|
};
|
|
283
343
|
|
|
@@ -290,6 +350,7 @@ const INSPECTION_BATCH_OUTPUT_SCHEMA = {
|
|
|
290
350
|
z.object({
|
|
291
351
|
inspection_url: z.string(),
|
|
292
352
|
inspection_result: z.unknown().optional(),
|
|
353
|
+
index_status_summary: URL_INSPECTION_INDEX_STATUS_SUMMARY_SCHEMA.optional(),
|
|
293
354
|
error: z.string().optional(),
|
|
294
355
|
}),
|
|
295
356
|
),
|
|
@@ -361,6 +422,22 @@ const SITE_DELETE_OUTPUT_SCHEMA = {
|
|
|
361
422
|
provider_scope_note: z.string(),
|
|
362
423
|
};
|
|
363
424
|
|
|
425
|
+
const SITEMAP_SUBMIT_OUTPUT_SCHEMA = {
|
|
426
|
+
message: z.string(),
|
|
427
|
+
submitted_to_search_console: z.literal(true),
|
|
428
|
+
sitemap_processing_completion_proven: z.literal(false),
|
|
429
|
+
page_indexing_proven: z.literal(false),
|
|
430
|
+
provider_scope_note: z.string(),
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const SITEMAP_DELETE_OUTPUT_SCHEMA = {
|
|
434
|
+
message: z.string(),
|
|
435
|
+
removed_from_search_console: z.literal(true),
|
|
436
|
+
sitemap_file_deleted: z.literal(false),
|
|
437
|
+
page_deindexing_proven: z.literal(false),
|
|
438
|
+
provider_scope_note: z.string(),
|
|
439
|
+
};
|
|
440
|
+
|
|
364
441
|
const SEARCH_ANALYTICS_PAGINATION_OUTPUT_SCHEMA = z.object({
|
|
365
442
|
rows_fetched: z.number().int().nonnegative(),
|
|
366
443
|
pages_fetched: z.number().int().positive(),
|
|
@@ -715,12 +792,12 @@ const TOOL_CATALOG = [
|
|
|
715
792
|
{
|
|
716
793
|
name: 'urls.inspect',
|
|
717
794
|
description:
|
|
718
|
-
"Inspect Google's indexed version of a single URL: index status, last crawl, page-fetch/indexing state, canonicals, and rich-results/AMP analysis where available. This is not a live URL test; mobile usability is a deprecated response field. Use urls.inspect_many for 2-10 URLs.",
|
|
795
|
+
"Inspect Google's indexed version of a single URL: a conservative normalized index-status summary plus Google's raw index status, last crawl, page-fetch/indexing state, canonicals, and rich-results/AMP analysis where available. This is not a live URL test; mobile usability is a deprecated response field. Use urls.inspect_many for 2-10 URLs.",
|
|
719
796
|
},
|
|
720
797
|
{
|
|
721
798
|
name: 'urls.inspect_many',
|
|
722
799
|
description:
|
|
723
|
-
`Inspect Google's indexed versions of up to 10 URLs with bounded concurrency of ${URL_INSPECTION_BATCH_CONCURRENCY} in one MCP call while charging the same URL Inspection safety budget per URL. This does not run live URL tests.`,
|
|
800
|
+
`Inspect Google's indexed versions of up to 10 URLs with a conservative normalized index-status summary per successful item and bounded concurrency of ${URL_INSPECTION_BATCH_CONCURRENCY} in one MCP call while charging the same URL Inspection safety budget per URL. This does not run live URL tests.`,
|
|
724
801
|
},
|
|
725
802
|
{
|
|
726
803
|
name: 'sitemaps.list',
|
|
@@ -730,12 +807,12 @@ const TOOL_CATALOG = [
|
|
|
730
807
|
{
|
|
731
808
|
name: 'sitemaps.submit',
|
|
732
809
|
description:
|
|
733
|
-
'Submit a
|
|
810
|
+
'Submit a sitemap URL to a Search Console property. A successful API response proves the Search Console submission request succeeded; it does not prove sitemap processing completed or that any page was indexed.',
|
|
734
811
|
},
|
|
735
812
|
{
|
|
736
813
|
name: 'sitemaps.delete',
|
|
737
814
|
description:
|
|
738
|
-
'Remove
|
|
815
|
+
'Remove a submitted sitemap from a Search Console property. This removes the Search Console sitemap entry; it does not delete the remotely hosted sitemap file or prove that any page was deindexed.',
|
|
739
816
|
},
|
|
740
817
|
{
|
|
741
818
|
name: 'sitemaps.get',
|
|
@@ -914,7 +991,7 @@ class GscMcpRuntime {
|
|
|
914
991
|
{
|
|
915
992
|
title: 'List Search Console properties',
|
|
916
993
|
description:
|
|
917
|
-
"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.",
|
|
994
|
+
"List the Google Search Console properties (sites) the connected Google account has access to. Each entry preserves Google's siteUrl and permissionLevel, derives verification_state plus owner from Google's documented permission levels, and adds api_identifier_kind plus mcp_site_url_accepted. Treat verification_state='unverified' as not ready for normal property-scoped Search Console workflows; owner=true is required by this server's restricted Indexing API workflows. 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.",
|
|
918
995
|
inputSchema: {},
|
|
919
996
|
outputSchema: SITES_OUTPUT_SCHEMA,
|
|
920
997
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
@@ -927,6 +1004,7 @@ class GscMcpRuntime {
|
|
|
927
1004
|
const mcpSiteUrlAccepted = apiIdentifierKind !== 'undocumented';
|
|
928
1005
|
return {
|
|
929
1006
|
...site,
|
|
1007
|
+
...sitePermissionState(site.permissionLevel),
|
|
930
1008
|
api_identifier_kind: apiIdentifierKind,
|
|
931
1009
|
mcp_site_url_accepted: mcpSiteUrlAccepted,
|
|
932
1010
|
...(!mcpSiteUrlAccepted
|
|
@@ -943,7 +1021,7 @@ class GscMcpRuntime {
|
|
|
943
1021
|
{
|
|
944
1022
|
title: 'Get Search Console property',
|
|
945
1023
|
description:
|
|
946
|
-
"Retrieve one exact Google Search Console property and
|
|
1024
|
+
"Retrieve one exact Google Search Console property and its connected-account access state. The response preserves Google's permissionLevel and derives verification_state plus owner so callers can distinguish an unverified property and recognize owner-only Indexing API prerequisites without listing every property first.",
|
|
947
1025
|
inputSchema: {
|
|
948
1026
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
949
1027
|
},
|
|
@@ -954,7 +1032,13 @@ class GscMcpRuntime {
|
|
|
954
1032
|
const googleId = this.requireGoogleId();
|
|
955
1033
|
const accessToken = await this.getAccessToken(googleId);
|
|
956
1034
|
const site = await getSite(accessToken, site_url);
|
|
957
|
-
|
|
1035
|
+
const siteWithPermissionState = {
|
|
1036
|
+
...site,
|
|
1037
|
+
...sitePermissionState(site.permissionLevel),
|
|
1038
|
+
};
|
|
1039
|
+
return toolResponse(JSON.stringify(siteWithPermissionState, null, 2), {
|
|
1040
|
+
site: siteWithPermissionState,
|
|
1041
|
+
});
|
|
958
1042
|
},
|
|
959
1043
|
);
|
|
960
1044
|
|
|
@@ -962,7 +1046,7 @@ class GscMcpRuntime {
|
|
|
962
1046
|
'urls.inspect',
|
|
963
1047
|
{
|
|
964
1048
|
title: 'Inspect URL index status',
|
|
965
|
-
description: `Inspect the version of a single URL currently known in Google's index. Returns Google's index-status analysis, last crawl, page-fetch/indexing state, canonicals, and rich-results/AMP analysis where available. This API does not test the live URL or prove current live-page indexability, and its mobile-usability result is deprecated. Use this when the user asks 'is X indexed?' or wants Google's indexed-state evidence for one page. For a bounded group of 2-10 URLs, prefer urls.inspect_many; Google still processes one URL Inspection request per URL and applies the same quota semantics.`,
|
|
1049
|
+
description: `Inspect the version of a single URL currently known in Google's index. Returns a conservative normalized index-status summary alongside Google's raw index-status analysis, last crawl, page-fetch/indexing state, canonicals, and rich-results/AMP analysis where available. This API does not test the live URL or prove current live-page indexability, and its mobile-usability result is deprecated. Use this when the user asks 'is X indexed?' or wants Google's indexed-state evidence for one page. For a bounded group of 2-10 URLs, prefer urls.inspect_many; Google still processes one URL Inspection request per URL and applies the same quota semantics.`,
|
|
966
1050
|
inputSchema: {
|
|
967
1051
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
968
1052
|
inspection_url: z
|
|
@@ -991,8 +1075,10 @@ class GscMcpRuntime {
|
|
|
991
1075
|
inspection_url,
|
|
992
1076
|
language_code,
|
|
993
1077
|
);
|
|
994
|
-
|
|
1078
|
+
const indexStatusSummary = summarizeUrlInspectionIndexStatus(result);
|
|
1079
|
+
return toolResponse(JSON.stringify({ note: URL_INSPECTION_SCOPE_NOTE, index_status_summary: indexStatusSummary, inspection_result: result }, null, 2), {
|
|
995
1080
|
inspection_result: result,
|
|
1081
|
+
index_status_summary: indexStatusSummary,
|
|
996
1082
|
note: URL_INSPECTION_SCOPE_NOTE,
|
|
997
1083
|
});
|
|
998
1084
|
},
|
|
@@ -1002,7 +1088,7 @@ class GscMcpRuntime {
|
|
|
1002
1088
|
'urls.inspect_many',
|
|
1003
1089
|
{
|
|
1004
1090
|
title: 'Inspect multiple URLs',
|
|
1005
|
-
description: `Inspect the versions of up to 10 URLs currently known in Google's index from one Search Console property. This API does not run live URL tests. Google still processes one URL Inspection request per URL, so every requested URL consumes one quota unit and one unit of this server's shared URL-inspection safety budget. Requests use bounded concurrency of ${URL_INSPECTION_BATCH_CONCURRENCY}; results preserve input order, and a Google-access revocation stops later chunks from starting. Use this for a small group of important or debugging-target URLs; do not use it to crawl an entire site.`,
|
|
1091
|
+
description: `Inspect the versions of up to 10 URLs currently known in Google's index from one Search Console property. Each successful item includes a conservative normalized index-status summary alongside Google's raw response. This API does not run live URL tests. Google still processes one URL Inspection request per URL, so every requested URL consumes one quota unit and one unit of this server's shared URL-inspection safety budget. Requests use bounded concurrency of ${URL_INSPECTION_BATCH_CONCURRENCY}; results preserve input order, and a Google-access revocation stops later chunks from starting. Use this for a small group of important or debugging-target URLs; do not use it to crawl an entire site.`,
|
|
1006
1092
|
inputSchema: {
|
|
1007
1093
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
1008
1094
|
inspection_urls: z
|
|
@@ -1046,7 +1132,12 @@ class GscMcpRuntime {
|
|
|
1046
1132
|
const results = batchResults.map((result) => ({
|
|
1047
1133
|
inspection_url: result.inspectionUrl,
|
|
1048
1134
|
...(result.inspectionResult !== undefined
|
|
1049
|
-
? {
|
|
1135
|
+
? {
|
|
1136
|
+
inspection_result: result.inspectionResult,
|
|
1137
|
+
index_status_summary: summarizeUrlInspectionIndexStatus(
|
|
1138
|
+
result.inspectionResult,
|
|
1139
|
+
),
|
|
1140
|
+
}
|
|
1050
1141
|
: {}),
|
|
1051
1142
|
...(result.error !== undefined ? { error: result.error } : {}),
|
|
1052
1143
|
}));
|
|
@@ -1556,12 +1647,12 @@ class GscMcpRuntime {
|
|
|
1556
1647
|
'sitemaps.submit',
|
|
1557
1648
|
{
|
|
1558
1649
|
title: 'Submit sitemap',
|
|
1559
|
-
description: 'Submit a
|
|
1650
|
+
description: 'Submit a sitemap URL to a Search Console property. A successful API response means the Search Console submission request succeeded; it does not prove sitemap processing completed or that any page was indexed.',
|
|
1560
1651
|
inputSchema: {
|
|
1561
1652
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
1562
1653
|
feedpath: SITEMAP_URL_SCHEMA.describe('The full HTTP/HTTPS URL of the sitemap file to submit, e.g. https://example.com/sitemap.xml'),
|
|
1563
1654
|
},
|
|
1564
|
-
outputSchema:
|
|
1655
|
+
outputSchema: SITEMAP_SUBMIT_OUTPUT_SCHEMA,
|
|
1565
1656
|
annotations: WRITE_TOOL_ANNOTATIONS['sitemaps.submit'],
|
|
1566
1657
|
},
|
|
1567
1658
|
async ({ site_url, feedpath }) => {
|
|
@@ -1570,8 +1661,15 @@ class GscMcpRuntime {
|
|
|
1570
1661
|
if (rateLimitError) return rateLimitError;
|
|
1571
1662
|
const accessToken = await this.getAccessToken(googleId);
|
|
1572
1663
|
await submitSitemap(accessToken, site_url, feedpath);
|
|
1573
|
-
const message = `Successfully submitted sitemap: ${feedpath} for site: ${site_url}`;
|
|
1574
|
-
|
|
1664
|
+
const message = `Successfully submitted sitemap to Search Console: ${feedpath} for site: ${site_url}`;
|
|
1665
|
+
const payload = {
|
|
1666
|
+
message,
|
|
1667
|
+
submitted_to_search_console: true as const,
|
|
1668
|
+
sitemap_processing_completion_proven: false as const,
|
|
1669
|
+
page_indexing_proven: false as const,
|
|
1670
|
+
provider_scope_note: "Google's Sitemaps.submit method returns an empty body on success. That success confirms the Search Console submission operation, not completed sitemap processing or page indexing.",
|
|
1671
|
+
};
|
|
1672
|
+
return toolResponse(`${message}\n\n${payload.provider_scope_note}`, payload);
|
|
1575
1673
|
},
|
|
1576
1674
|
);
|
|
1577
1675
|
|
|
@@ -1579,12 +1677,12 @@ class GscMcpRuntime {
|
|
|
1579
1677
|
'sitemaps.delete',
|
|
1580
1678
|
{
|
|
1581
1679
|
title: 'Delete sitemap',
|
|
1582
|
-
description: 'Remove
|
|
1680
|
+
description: 'Remove a submitted sitemap from a Search Console property. This removes the Search Console sitemap entry; it does not delete the remotely hosted sitemap file or prove that any page was deindexed.',
|
|
1583
1681
|
inputSchema: {
|
|
1584
1682
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
1585
1683
|
feedpath: SITEMAP_URL_SCHEMA.describe('The full HTTP/HTTPS URL of the sitemap file to delete, e.g. https://example.com/sitemap.xml'),
|
|
1586
1684
|
},
|
|
1587
|
-
outputSchema:
|
|
1685
|
+
outputSchema: SITEMAP_DELETE_OUTPUT_SCHEMA,
|
|
1588
1686
|
annotations: WRITE_TOOL_ANNOTATIONS['sitemaps.delete'],
|
|
1589
1687
|
},
|
|
1590
1688
|
async ({ site_url, feedpath }) => {
|
|
@@ -1593,8 +1691,15 @@ class GscMcpRuntime {
|
|
|
1593
1691
|
if (rateLimitError) return rateLimitError;
|
|
1594
1692
|
const accessToken = await this.getAccessToken(googleId);
|
|
1595
1693
|
await deleteSitemap(accessToken, site_url, feedpath);
|
|
1596
|
-
const message = `Successfully
|
|
1597
|
-
|
|
1694
|
+
const message = `Successfully removed sitemap from Search Console: ${feedpath} for site: ${site_url}`;
|
|
1695
|
+
const payload = {
|
|
1696
|
+
message,
|
|
1697
|
+
removed_from_search_console: true as const,
|
|
1698
|
+
sitemap_file_deleted: false as const,
|
|
1699
|
+
page_deindexing_proven: false as const,
|
|
1700
|
+
provider_scope_note: "Google's Sitemaps.delete method removes the submitted sitemap from the Search Console property. It does not delete the remotely hosted sitemap file or prove that any page was deindexed.",
|
|
1701
|
+
};
|
|
1702
|
+
return toolResponse(`${message}\n\n${payload.provider_scope_note}`, payload);
|
|
1598
1703
|
},
|
|
1599
1704
|
);
|
|
1600
1705
|
}
|