@digestseo/mcp-gsc 0.4.30 → 0.4.31
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 +10 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/google.ts +22 -0
- package/src/index.ts +73 -23
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.31] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- 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.
|
|
17
|
+
- `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.
|
|
18
|
+
|
|
10
19
|
## [0.4.30] - 2026-09-23
|
|
11
20
|
|
|
12
21
|
### Changed
|
|
@@ -198,6 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
198
207
|
## [0.4.2] - 2026-09-21
|
|
199
208
|
|
|
200
209
|
### Fixed
|
|
210
|
+
- 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
211
|
- 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
212
|
- 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
213
|
- 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 --ref main
|
|
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) {
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
requestIndexingRemoval,
|
|
33
33
|
getIndexingNotificationMetadata,
|
|
34
34
|
processPerformanceComparison,
|
|
35
|
+
assertSearchAnalyticsFilterCompatible,
|
|
35
36
|
type PaginatedSearchAnalyticsResult,
|
|
36
37
|
} from './google';
|
|
37
38
|
import {
|
|
@@ -149,35 +150,57 @@ const SEARCH_ROW_OUTPUT_SCHEMA = {
|
|
|
149
150
|
),
|
|
150
151
|
};
|
|
151
152
|
|
|
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
|
-
|
|
153
|
+
const SEARCH_ANALYTICS_FILTER_SCHEMA = z
|
|
154
|
+
.object({
|
|
155
|
+
dimension: z.enum([
|
|
156
|
+
'query',
|
|
157
|
+
'page',
|
|
158
|
+
'country',
|
|
159
|
+
'device',
|
|
160
|
+
'searchAppearance',
|
|
161
|
+
]),
|
|
162
|
+
operator: z
|
|
163
|
+
.enum([
|
|
164
|
+
'equals',
|
|
165
|
+
'notEquals',
|
|
166
|
+
'contains',
|
|
167
|
+
'notContains',
|
|
168
|
+
'includingRegex',
|
|
169
|
+
'excludingRegex',
|
|
170
|
+
])
|
|
171
|
+
.default('equals'),
|
|
172
|
+
expression: z.string().max(4096),
|
|
173
|
+
})
|
|
174
|
+
.superRefine((filter, ctx) => {
|
|
175
|
+
try {
|
|
176
|
+
assertSearchAnalyticsFilterCompatible(filter);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
ctx.addIssue({
|
|
179
|
+
code: 'custom',
|
|
180
|
+
path: ['expression'],
|
|
181
|
+
message: (error as Error).message,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
172
185
|
|
|
173
186
|
const SEARCH_ANALYTICS_FILTER_GROUP_SCHEMA = z.object({
|
|
174
187
|
groupType: z.literal('and').default('and'),
|
|
175
188
|
filters: z.array(SEARCH_ANALYTICS_FILTER_SCHEMA),
|
|
176
189
|
});
|
|
177
190
|
|
|
191
|
+
const SITE_VERIFICATION_STATE_SCHEMA = z
|
|
192
|
+
.enum(['verified', 'unverified', 'unknown'])
|
|
193
|
+
.describe(
|
|
194
|
+
"Derived from Google's permissionLevel. siteOwner/siteFullUser/siteRestrictedUser are verified, siteUnverifiedUser is unverified, and any future undocumented permission level is reported as unknown.",
|
|
195
|
+
);
|
|
196
|
+
|
|
178
197
|
const SITE_OUTPUT_SCHEMA = {
|
|
179
198
|
siteUrl: z.string(),
|
|
180
199
|
permissionLevel: z.string(),
|
|
200
|
+
verification_state: SITE_VERIFICATION_STATE_SCHEMA,
|
|
201
|
+
owner: z.boolean().describe(
|
|
202
|
+
"True only when Google returns permissionLevel 'siteOwner'. Restricted Indexing API workflows require owner-level Search Console access.",
|
|
203
|
+
),
|
|
181
204
|
};
|
|
182
205
|
|
|
183
206
|
const LISTED_SITE_OUTPUT_SCHEMA = {
|
|
@@ -190,6 +213,26 @@ const LISTED_SITE_OUTPUT_SCHEMA = {
|
|
|
190
213
|
const PLATFORM_PROPERTY_API_NOTE =
|
|
191
214
|
"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
215
|
|
|
216
|
+
function sitePermissionState(permissionLevel: string): {
|
|
217
|
+
verification_state: 'verified' | 'unverified' | 'unknown';
|
|
218
|
+
owner: boolean;
|
|
219
|
+
} {
|
|
220
|
+
if (permissionLevel === 'siteUnverifiedUser') {
|
|
221
|
+
return { verification_state: 'unverified', owner: false };
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
permissionLevel === 'siteOwner' ||
|
|
225
|
+
permissionLevel === 'siteFullUser' ||
|
|
226
|
+
permissionLevel === 'siteRestrictedUser'
|
|
227
|
+
) {
|
|
228
|
+
return {
|
|
229
|
+
verification_state: 'verified',
|
|
230
|
+
owner: permissionLevel === 'siteOwner',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return { verification_state: 'unknown', owner: false };
|
|
234
|
+
}
|
|
235
|
+
|
|
193
236
|
const SITEMAP_PROVIDER_NOTE =
|
|
194
237
|
"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
238
|
|
|
@@ -914,7 +957,7 @@ class GscMcpRuntime {
|
|
|
914
957
|
{
|
|
915
958
|
title: 'List Search Console properties',
|
|
916
959
|
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.",
|
|
960
|
+
"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
961
|
inputSchema: {},
|
|
919
962
|
outputSchema: SITES_OUTPUT_SCHEMA,
|
|
920
963
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
@@ -927,6 +970,7 @@ class GscMcpRuntime {
|
|
|
927
970
|
const mcpSiteUrlAccepted = apiIdentifierKind !== 'undocumented';
|
|
928
971
|
return {
|
|
929
972
|
...site,
|
|
973
|
+
...sitePermissionState(site.permissionLevel),
|
|
930
974
|
api_identifier_kind: apiIdentifierKind,
|
|
931
975
|
mcp_site_url_accepted: mcpSiteUrlAccepted,
|
|
932
976
|
...(!mcpSiteUrlAccepted
|
|
@@ -943,7 +987,7 @@ class GscMcpRuntime {
|
|
|
943
987
|
{
|
|
944
988
|
title: 'Get Search Console property',
|
|
945
989
|
description:
|
|
946
|
-
"Retrieve one exact Google Search Console property and
|
|
990
|
+
"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
991
|
inputSchema: {
|
|
948
992
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
949
993
|
},
|
|
@@ -954,7 +998,13 @@ class GscMcpRuntime {
|
|
|
954
998
|
const googleId = this.requireGoogleId();
|
|
955
999
|
const accessToken = await this.getAccessToken(googleId);
|
|
956
1000
|
const site = await getSite(accessToken, site_url);
|
|
957
|
-
|
|
1001
|
+
const siteWithPermissionState = {
|
|
1002
|
+
...site,
|
|
1003
|
+
...sitePermissionState(site.permissionLevel),
|
|
1004
|
+
};
|
|
1005
|
+
return toolResponse(JSON.stringify(siteWithPermissionState, null, 2), {
|
|
1006
|
+
site: siteWithPermissionState,
|
|
1007
|
+
});
|
|
958
1008
|
},
|
|
959
1009
|
);
|
|
960
1010
|
|