@digestseo/mcp-gsc 0.4.27 → 0.4.29
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 +16 -0
- package/README.md +11 -2
- package/package.json +1 -1
- package/src/google.ts +25 -0
- package/src/index.ts +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.29] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `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.
|
|
14
|
+
- `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.
|
|
15
|
+
|
|
16
|
+
## [0.4.28] - 2026-09-23
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- GitHub Copilot CLI users can now register this repository as the `digestseo` plugin marketplace, discover/install `mcp-gsc` through the marketplace workflow, and still use the supported direct-repository install path.
|
|
20
|
+
- OpenAI Codex CLI and IDE users now have a verified one-command Streamable HTTP setup for the local `mcp-gsc` launcher.
|
|
21
|
+
- MCP initialization now supplies concise server-level workflow instructions so compatible agents preserve Search Console pagination, evidence, and Indexing API semantics across tools.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Search Analytics `quotaExceeded` load failures now return provider-specific recovery guidance instead of an opaque raw 403 payload, while remaining outside the short transient retry loop because Google's documented recovery window is measured in minutes rather than seconds.
|
|
25
|
+
|
|
10
26
|
## [0.4.27] - 2026-09-23
|
|
11
27
|
|
|
12
28
|
### Added
|
package/README.md
CHANGED
|
@@ -52,6 +52,14 @@ Self-hosted Cloudflare Worker deployments continue to use `https://<your-worker>
|
|
|
52
52
|
claude mcp add --transport http gsc http://127.0.0.1:8080/mcp
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
**OpenAI Codex CLI / IDE**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
codex mcp add gsc --url http://127.0.0.1:8080/mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Codex CLI and the Codex IDE extension share MCP configuration, so registering the server once makes the same local connection available to both clients. Start the npm launcher before using the connection; Codex will open the server's OAuth flow when authentication is required.
|
|
62
|
+
|
|
55
63
|
**Cursor**
|
|
56
64
|
|
|
57
65
|
[](https://cursor.com/en/install-mcp?name=gsc&config=eyJ1cmwiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvbWNwIn0%3D)
|
|
@@ -61,10 +69,11 @@ When this repository is installed as a Cursor plugin, Cursor also auto-discovers
|
|
|
61
69
|
**GitHub Copilot CLI**
|
|
62
70
|
|
|
63
71
|
```bash
|
|
64
|
-
copilot plugin
|
|
72
|
+
copilot plugin marketplace add AKzar1el/mcp-gsc
|
|
73
|
+
copilot plugin install mcp-gsc@digestseo
|
|
65
74
|
```
|
|
66
75
|
|
|
67
|
-
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.
|
|
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.
|
|
68
77
|
|
|
69
78
|
**Kiro**
|
|
70
79
|
|
package/package.json
CHANGED
package/src/google.ts
CHANGED
|
@@ -62,6 +62,19 @@ const GOOGLE_READ_MAX_ATTEMPTS = 3;
|
|
|
62
62
|
const GOOGLE_READ_BASE_DELAY_MS = 1_000;
|
|
63
63
|
const GOOGLE_READ_MAX_DELAY_MS = 5_000;
|
|
64
64
|
|
|
65
|
+
function googleErrorReasonsFromText(text: string): string[] {
|
|
66
|
+
try {
|
|
67
|
+
const data = JSON.parse(text) as {
|
|
68
|
+
error?: { errors?: Array<{ reason?: unknown }> };
|
|
69
|
+
};
|
|
70
|
+
return (data.error?.errors ?? [])
|
|
71
|
+
.map((entry) => entry.reason)
|
|
72
|
+
.filter((reason): reason is string => typeof reason === 'string');
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
async function isGoogleReadRetryableResponse(response: Response): Promise<boolean> {
|
|
66
79
|
if (GOOGLE_READ_RETRYABLE_STATUSES.has(response.status)) return true;
|
|
67
80
|
if (response.status !== 403) return false;
|
|
@@ -358,6 +371,12 @@ export function assertSearchAnalyticsQueryCompatible(body: SearchAnalyticsQuery)
|
|
|
358
371
|
}
|
|
359
372
|
}
|
|
360
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
|
+
|
|
361
380
|
if (body.aggregationType === 'byProperty') {
|
|
362
381
|
if (hasPageGroupingOrFilter) {
|
|
363
382
|
throw new Error(
|
|
@@ -645,6 +664,12 @@ export async function querySearchAnalytics(
|
|
|
645
664
|
}
|
|
646
665
|
if (!resp.ok) {
|
|
647
666
|
const text = await resp.text();
|
|
667
|
+
const reasons = googleErrorReasonsFromText(text);
|
|
668
|
+
if (resp.status === 403 && reasons.includes('quotaExceeded')) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
'Search Analytics load quota exceeded. Google recommends waiting about 15 minutes before retrying a short-term load-quota failure. If the error persists, reduce expensive page/query grouping or filtering, shorten the date range, and avoid repeatedly requesting the same data.',
|
|
671
|
+
);
|
|
672
|
+
}
|
|
648
673
|
throw new Error(`Search analytics query failed: ${resp.status} ${text}`);
|
|
649
674
|
}
|
|
650
675
|
const data = (await resp.json()) as {
|
package/src/index.ts
CHANGED
|
@@ -111,6 +111,8 @@ export interface AgentProps extends Record<string, unknown> {
|
|
|
111
111
|
|
|
112
112
|
const SERVER_NAME = 'mcp-gsc';
|
|
113
113
|
const SERVER_VERSION = pkg.version;
|
|
114
|
+
const SERVER_INSTRUCTIONS =
|
|
115
|
+
'If the exact Search Console site_url is unknown, call sites.list before property-scoped tools. Search Analytics can omit rows: page while has_more is true, and never treat a row missing from one bounded period as zero traffic. indexing.list_pages reports Search performance visibility, not index coverage; use urls.inspect or urls.inspect_many for indexed-state evidence. The Indexing API is restricted to eligible JobPosting/livestream pages; a receipt is not proof of indexing or removal.';
|
|
114
116
|
|
|
115
117
|
const NOT_AUTHENTICATED_MESSAGE =
|
|
116
118
|
`Not authenticated. ${MCP_RECONNECT_INSTRUCTION}`;
|
|
@@ -765,10 +767,15 @@ const TOOL_CATALOG = [
|
|
|
765
767
|
] as const;
|
|
766
768
|
|
|
767
769
|
class GscMcpRuntime {
|
|
768
|
-
server = new McpServer(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
770
|
+
server = new McpServer(
|
|
771
|
+
{
|
|
772
|
+
name: SERVER_NAME,
|
|
773
|
+
version: SERVER_VERSION,
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
777
|
+
},
|
|
778
|
+
);
|
|
772
779
|
|
|
773
780
|
constructor(
|
|
774
781
|
private readonly env: Env,
|
|
@@ -980,8 +987,11 @@ class GscMcpRuntime {
|
|
|
980
987
|
.array(z.string().url())
|
|
981
988
|
.min(1)
|
|
982
989
|
.max(10)
|
|
990
|
+
.refine((urls) => new Set(urls).size === urls.length, {
|
|
991
|
+
message: 'inspection_urls must not contain duplicate URLs.',
|
|
992
|
+
})
|
|
983
993
|
.describe(
|
|
984
|
-
'Between 1 and 10 fully-qualified URLs to inspect. Each URL must belong to the site_url property.',
|
|
994
|
+
'Between 1 and 10 unique fully-qualified URLs to inspect. Each URL must belong to the site_url property.',
|
|
985
995
|
),
|
|
986
996
|
language_code: z
|
|
987
997
|
.string()
|
|
@@ -1209,7 +1219,7 @@ class GscMcpRuntime {
|
|
|
1209
1219
|
.enum(['auto', 'byNewsShowcasePanel', 'byPage', 'byProperty'])
|
|
1210
1220
|
.default('auto')
|
|
1211
1221
|
.describe(
|
|
1212
|
-
"How Google aggregates metrics. Leave as 'auto' unless specific semantics are needed. 'byProperty'
|
|
1222
|
+
"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.",
|
|
1213
1223
|
),
|
|
1214
1224
|
dimension_filter_groups: z
|
|
1215
1225
|
.array(
|