@digestseo/mcp-gsc 0.4.12 → 0.4.13
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 +7 -0
- package/README.md +5 -1
- package/SETUP.md +1 -0
- package/package.json +1 -1
- package/scripts/glama-start.mjs +8 -0
- package/src/index.ts +30 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.13] - 2026-09-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- The published `npx` launcher now persists local Wrangler/KV/Durable Object state in a stable per-user directory instead of the versioned npm package cache, preserving the encrypted Google session across later package upgrades and ordinary npm cache cleanup; `MCP_GSC_STATE_DIR` can override the location. The first release with this change may require one Google reconnect because older launcher releases used the package cache for local state.
|
|
14
|
+
- Official MCP Registry discovery no longer advertises the owner-hosted remote while that deployment's tool contract lags the current npm package; the verified npm launcher remains the Registry installation path until hosted parity is restored.
|
|
15
|
+
- Search Analytics now exposes a machine-readable Generative AI report boundary instead of inviting agents to invent an unsupported selector: the current documented API has no dedicated Generative AI search type/filter, while AI Overviews and AI Mode remain included in overall web Search performance data.
|
|
16
|
+
|
|
10
17
|
## [0.4.12] - 2026-09-22
|
|
11
18
|
|
|
12
19
|
### Fixed
|
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ npx -y @digestseo/mcp-gsc
|
|
|
24
24
|
|
|
25
25
|
The npm launcher starts a loopback-only **Streamable HTTP** Worker on `127.0.0.1` (port `8080` by default); it is not a stdio MCP process. You can run the `npx` command from any directory because the launcher resolves its bundled Worker configuration from the installed package. Clients that launch the package should connect to `http://127.0.0.1:8080/mcp` after supplying the Google OAuth and token-encryption environment variables described in [SETUP.md](SETUP.md). Before the first OAuth sign-in, authorize the exact callback `http://127.0.0.1:8080/google/callback` in Google Cloud (or use the same custom `PORT` you launch with); the launcher prints both URLs at startup.
|
|
26
26
|
|
|
27
|
+
Local KV and Durable Object state, including the encrypted Google session, is stored in `~/.mcp-gsc/state` by default instead of inside the versioned npm cache. That keeps the local connection available across package upgrades and ordinary npm cache cleanup. Set `MCP_GSC_STATE_DIR` to use another directory; keep the same `TOKEN_ENCRYPTION_KEY` when reusing existing state because stored refresh tokens are encrypted with that key. The launcher prints the effective state directory at startup. The first release with this stable path may require one Google reconnect because older launcher releases stored local state under the npm package cache.
|
|
28
|
+
|
|
27
29
|
The local launcher defaults to `GSC_ACCESS_MODE=readwrite`. For least-privilege analytics/reporting-only use, set `GSC_ACCESS_MODE=readonly` in the environment before running `npx`; the launcher forwards that value into the local Worker so OAuth requests only the read-only Search Console scope and the five mutation tools stay unregistered.
|
|
28
30
|
|
|
29
31
|
For Google OAuth and Cloudflare deployment configuration, follow [SETUP.md](SETUP.md).
|
|
@@ -75,7 +77,7 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-onl
|
|
|
75
77
|
| **`server.capabilities`** | Read | List every tool this server exposes and report whether your Google connection is currently authenticated (`connected` / `not_connected`). Takes no arguments — a good first call for discovery. |
|
|
76
78
|
| **`sites.list`** | Read | List the Search Console properties the connected Google account can access (`siteUrl`, `permissionLevel`). |
|
|
77
79
|
| **`sites.get`** | Read | Retrieve one exact Search Console property and the connected account's permission level for it. |
|
|
78
|
-
| **`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. 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, and query grouping/filtering is unavailable. |
|
|
80
|
+
| **`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. 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, 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`. |
|
|
79
81
|
| **`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. |
|
|
80
82
|
| **`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. |
|
|
81
83
|
| **`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. |
|
|
@@ -110,6 +112,8 @@ Once connected, ask your assistant things like:
|
|
|
110
112
|
|
|
111
113
|
Query regexes are a manual approximation only. Search Console's native Branded/Non-branded filter uses Google's own AI-assisted classification (including language variants, typos, and associated products/services), and that native classifier is not exposed as a Search Analytics API filter.
|
|
112
114
|
|
|
115
|
+
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.
|
|
116
|
+
|
|
113
117
|
## Connect in your AI client
|
|
114
118
|
|
|
115
119
|
Once you've deployed the server (see **[SETUP.md](SETUP.md)**), connect it by pasting your Worker's `/mcp` URL into your client:
|
package/SETUP.md
CHANGED
|
@@ -81,6 +81,7 @@ npx wrangler login
|
|
|
81
81
|
- Replace `<your-worker>` with your Worker's name + subdomain. If you don't know it yet, deploy once (Step 6) to see the assigned `*.workers.dev` URL, then come back and add it here. The default Worker name is `mcp-gsc` (set in `wrangler.jsonc`).
|
|
82
82
|
- The `http://localhost:8787/google/callback` entry is for local development with `npm run dev`.
|
|
83
83
|
- The `http://127.0.0.1:8080/google/callback` entry is for the published `npx -y @digestseo/mcp-gsc` launcher, whose Registry transport is `http://127.0.0.1:8080/mcp` by default. If you set `PORT`, replace `8080` with that exact port.
|
|
84
|
+
- The `npx` launcher persists its local KV and Durable Object state in `~/.mcp-gsc/state` by default so npm package upgrades or cache cleanup do not discard the encrypted Google session. Set `MCP_GSC_STATE_DIR` to use another directory, and keep the same `TOKEN_ENCRYPTION_KEY` when reusing existing state. The first release with this stable path may require one Google reconnect because older launcher releases stored local state under the npm package cache.
|
|
84
85
|
- The Worker derives Google's redirect URI from the MCP request URL. Google requires an exact authorized-URI match, so keep the same scheme, host, port, and `/google/callback` path. `localhost` and `127.0.0.1` are different hosts for this check.
|
|
85
86
|
- The path must be exactly `/google/callback` — that's the route this server handles.
|
|
86
87
|
- The DigestSEO-hosted instance also requires this exact callback URI in the OAuth client configured on its Worker: `https://mcp-gsc.digestseo.com/google/callback`.
|
package/package.json
CHANGED
package/scripts/glama-start.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
3
5
|
import { fileURLToPath } from 'node:url';
|
|
4
6
|
|
|
5
7
|
const port = process.env.PORT || '8080';
|
|
@@ -9,6 +11,9 @@ const googleRedirectUri = `${localBaseUrl}/google/callback`;
|
|
|
9
11
|
const wranglerConfig = fileURLToPath(
|
|
10
12
|
new URL('../wrangler.example.jsonc', import.meta.url),
|
|
11
13
|
);
|
|
14
|
+
const stateDir = process.env.MCP_GSC_STATE_DIR
|
|
15
|
+
? resolve(process.env.MCP_GSC_STATE_DIR)
|
|
16
|
+
: resolve(homedir(), '.mcp-gsc', 'state');
|
|
12
17
|
const forwardedVariables = [
|
|
13
18
|
'GOOGLE_CLIENT_ID',
|
|
14
19
|
'GOOGLE_CLIENT_SECRET',
|
|
@@ -20,6 +25,8 @@ const args = [
|
|
|
20
25
|
'dev',
|
|
21
26
|
'--config',
|
|
22
27
|
wranglerConfig,
|
|
28
|
+
'--persist-to',
|
|
29
|
+
stateDir,
|
|
23
30
|
'--local',
|
|
24
31
|
'--ip',
|
|
25
32
|
'127.0.0.1',
|
|
@@ -35,6 +42,7 @@ for (const name of forwardedVariables) {
|
|
|
35
42
|
|
|
36
43
|
console.error(`[mcp-gsc] Local MCP endpoint: ${mcpUrl}`);
|
|
37
44
|
console.error(`[mcp-gsc] Google OAuth redirect URI: ${googleRedirectUri}`);
|
|
45
|
+
console.error(`[mcp-gsc] Local state directory: ${stateDir}`);
|
|
38
46
|
|
|
39
47
|
const wranglerCli = createRequire(import.meta.url).resolve('wrangler');
|
|
40
48
|
const child = spawn(process.execPath, [wranglerCli, ...args], { stdio: 'inherit' });
|
package/src/index.ts
CHANGED
|
@@ -193,6 +193,12 @@ const CAPABILITIES_OUTPUT_SCHEMA = {
|
|
|
193
193
|
description: z.string(),
|
|
194
194
|
}),
|
|
195
195
|
),
|
|
196
|
+
provider_limits: z.object({
|
|
197
|
+
generative_ai_performance_report: z.object({
|
|
198
|
+
dedicated_api_supported: z.literal(false),
|
|
199
|
+
note: z.string(),
|
|
200
|
+
}),
|
|
201
|
+
}),
|
|
196
202
|
hint: z.string(),
|
|
197
203
|
};
|
|
198
204
|
|
|
@@ -243,6 +249,10 @@ const SEARCH_ANALYTICS_OUTPUT_SCHEMA = {
|
|
|
243
249
|
'False because the Search Analytics API does not guarantee every data row; Google can return only top rows even after local pagination is exhausted.',
|
|
244
250
|
),
|
|
245
251
|
provider_note: z.string(),
|
|
252
|
+
generative_ai_report_isolatable: z.literal(false).describe(
|
|
253
|
+
'False because the current documented Search Analytics API exposes no dedicated Generative AI report search type or filter selector.',
|
|
254
|
+
),
|
|
255
|
+
generative_ai_note: z.string(),
|
|
246
256
|
next_start_row: z.number().int().nonnegative().optional(),
|
|
247
257
|
has_more: z.boolean(),
|
|
248
258
|
truncated: z.boolean(),
|
|
@@ -480,6 +490,9 @@ function toolResponse<T extends Record<string, unknown>>(
|
|
|
480
490
|
const SEARCH_ANALYTICS_PROVIDER_NOTE =
|
|
481
491
|
'Google Search Analytics does not guarantee all data rows and can return only top rows. Local pagination fields describe what this server fetched or bounded; they do not prove provider-level exhaustiveness.';
|
|
482
492
|
|
|
493
|
+
const GENERATIVE_AI_REPORT_API_NOTE =
|
|
494
|
+
"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.";
|
|
495
|
+
|
|
483
496
|
function paginationMetadata(result: PaginatedSearchAnalyticsResult) {
|
|
484
497
|
return {
|
|
485
498
|
rows_fetched: result.rows.length,
|
|
@@ -570,7 +583,7 @@ const TOOL_CATALOG = [
|
|
|
570
583
|
{
|
|
571
584
|
name: 'analytics.query',
|
|
572
585
|
description:
|
|
573
|
-
'Query Search Console search analytics (impressions, clicks, CTR, average position) over a date range, broken down by query, page, country, device, date, or search appearance. Supports filters and pagination. Google does not guarantee every data row and can return only top rows.',
|
|
586
|
+
'Query Search Console search analytics (impressions, clicks, CTR, average position) over a date range, broken down by query, page, country, device, date, or search appearance. Supports filters and pagination. Google does not guarantee every data row and can return only top rows. The current documented API does not expose a dedicated Generative AI performance-report selector; do not invent one.',
|
|
574
587
|
},
|
|
575
588
|
{
|
|
576
589
|
name: 'insights.page_queries',
|
|
@@ -733,6 +746,12 @@ class GscMcpRuntime {
|
|
|
733
746
|
access_mode: accessMode,
|
|
734
747
|
auth_status: authStatus,
|
|
735
748
|
tools: getToolCatalogForAccessMode(TOOL_CATALOG, accessMode),
|
|
749
|
+
provider_limits: {
|
|
750
|
+
generative_ai_performance_report: {
|
|
751
|
+
dedicated_api_supported: false as const,
|
|
752
|
+
note: GENERATIVE_AI_REPORT_API_NOTE,
|
|
753
|
+
},
|
|
754
|
+
},
|
|
736
755
|
hint: "If auth_status is not 'connected', the user should reconnect this server in their MCP client to sign in with Google.",
|
|
737
756
|
};
|
|
738
757
|
return toolResponse(JSON.stringify(capabilities, null, 2), capabilities);
|
|
@@ -993,6 +1012,13 @@ class GscMcpRuntime {
|
|
|
993
1012
|
' value in a separate query and group by page/query/country/device/',
|
|
994
1013
|
' date/hour as needed. Do not guess new appearance identifiers;',
|
|
995
1014
|
' discover the values Google returns for the property first.',
|
|
1015
|
+
'- GENERATIVE AI REPORT: Search Console now has dedicated',
|
|
1016
|
+
' Generative AI performance reports in its UI, but the current',
|
|
1017
|
+
' documented Search Analytics API exposes no dedicated Generative',
|
|
1018
|
+
' AI search type or filter selector. AI Overviews and AI Mode remain',
|
|
1019
|
+
' included in overall web Search performance data. Do not guess a',
|
|
1020
|
+
' searchAppearance identifier or claim analytics.query isolates the',
|
|
1021
|
+
' dedicated Generative AI report.',
|
|
996
1022
|
'- Use dimension_filter_groups to filter by country, device, query',
|
|
997
1023
|
' content, page URL, or search feature. includingRegex and',
|
|
998
1024
|
' excludingRegex use RE2 syntax. A query regex can provide a manual',
|
|
@@ -1054,7 +1080,7 @@ class GscMcpRuntime {
|
|
|
1054
1080
|
search_type: z
|
|
1055
1081
|
.enum(['web', 'image', 'video', 'news', 'discover', 'googleNews'])
|
|
1056
1082
|
.default('web')
|
|
1057
|
-
.describe('Which search index to query. Defaults to web. Google Discover and Google News do not support query grouping/filtering or average position.'),
|
|
1083
|
+
.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.'),
|
|
1058
1084
|
aggregation_type: z
|
|
1059
1085
|
.enum(['auto', 'byNewsShowcasePanel', 'byPage', 'byProperty'])
|
|
1060
1086
|
.default('auto')
|
|
@@ -1148,6 +1174,8 @@ class GscMcpRuntime {
|
|
|
1148
1174
|
: {}),
|
|
1149
1175
|
provider_exhaustiveness_guaranteed: false,
|
|
1150
1176
|
provider_note: SEARCH_ANALYTICS_PROVIDER_NOTE,
|
|
1177
|
+
generative_ai_report_isolatable: false,
|
|
1178
|
+
generative_ai_note: GENERATIVE_AI_REPORT_API_NOTE,
|
|
1151
1179
|
has_more: bounded.resultPage.has_more,
|
|
1152
1180
|
truncated: bounded.resultPage.truncated,
|
|
1153
1181
|
byte_limit_reached: bounded.resultPage.byte_limit_reached,
|