@digestseo/mcp-gsc 0.4.13 → 0.4.15
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 +12 -0
- package/README.md +14 -12
- package/package.json +1 -1
- package/src/index.ts +34 -9
- package/src/write-tool-annotations.ts +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.15] - 2026-09-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `analytics.query` now distinguishes fresh preliminary Search Analytics data from finalized-data lag, echoes the requested `data_state`, and reports whether preliminary data is possible instead of implying a blanket 2-3 day delay.
|
|
14
|
+
- Repo-owned quick-connect configs and client docs now use the verified loopback npm launcher instead of routing new users to the intentionally unadvertised hosted remote while that deployment lags the current package.
|
|
15
|
+
|
|
16
|
+
## [0.4.14] - 2026-09-22
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `sites.add` now explicitly reports that adding a Search Console property does not perform ownership verification, preventing agents from confusing the Search Console Sites API with Google's separate Site Verification workflow.
|
|
20
|
+
- MCP write annotations now classify `sites.add` and `sitemaps.submit` as additive (`destructiveHint: false`) while keeping the delete tools destructive, matching the protocol's risk vocabulary instead of labeling every external mutation as destructive.
|
|
21
|
+
|
|
10
22
|
## [0.4.13] - 2026-09-22
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -30,35 +30,37 @@ The local launcher defaults to `GSC_ACCESS_MODE=readwrite`. For least-privilege
|
|
|
30
30
|
|
|
31
31
|
For Google OAuth and Cloudflare deployment configuration, follow [SETUP.md](SETUP.md).
|
|
32
32
|
|
|
33
|
-
### Connect
|
|
33
|
+
### Connect the local launcher
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
For the current verified quick-connect path, start the npm launcher first after supplying the Google OAuth and token-encryption environment variables described in [SETUP.md](SETUP.md):
|
|
36
36
|
|
|
37
|
-
```
|
|
38
|
-
|
|
37
|
+
```bash
|
|
38
|
+
npx -y @digestseo/mcp-gsc
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Then connect your MCP client to the launcher's loopback endpoint:
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
|
|
44
|
+
http://127.0.0.1:8080/mcp
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
Self-hosted Cloudflare Worker deployments continue to use `https://<your-worker>.workers.dev/mcp`. The repo does not currently advertise the owner-hosted remote as a quick-connect path because its deployed tool contract is behind the published package.
|
|
48
|
+
|
|
47
49
|
**Claude Code**
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
claude mcp add --transport http gsc
|
|
52
|
+
claude mcp add --transport http gsc http://127.0.0.1:8080/mcp
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
**Cursor**
|
|
54
56
|
|
|
55
|
-
[](https://cursor.com/en/install-mcp?name=gsc&config=
|
|
57
|
+
[](https://cursor.com/en/install-mcp?name=gsc&config=eyJ1cmwiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvbWNwIn0%3D)
|
|
56
58
|
|
|
57
59
|
**Kiro**
|
|
58
60
|
|
|
59
|
-
[](https://kiro.dev/launch/mcp/add?name=mcp-gsc&config=%7B%22url%22%3A%
|
|
61
|
+
[](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)
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
Start the npm launcher before using either install button. Complete its Google OAuth flow on first use.
|
|
62
64
|
|
|
63
65
|
**ChatGPT** — add it as a custom MCP app in developer mode; see [Connect in your AI client](#connect-in-your-ai-client) below.
|
|
64
66
|
|
|
@@ -77,7 +79,7 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-onl
|
|
|
77
79
|
| **`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. |
|
|
78
80
|
| **`sites.list`** | Read | List the Search Console properties the connected Google account can access (`siteUrl`, `permissionLevel`). |
|
|
79
81
|
| **`sites.get`** | Read | Retrieve one exact Search Console property and the connected account's permission level for it. |
|
|
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`. |
|
|
82
|
+
| **`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, 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`. |
|
|
81
83
|
| **`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. |
|
|
82
84
|
| **`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. |
|
|
83
85
|
| **`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. |
|
|
@@ -85,7 +87,7 @@ By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-onl
|
|
|
85
87
|
| **`insights.quick_wins`** / **`insights.cannibalization`** / **`insights.content_decay`** | Read | Surface average-position opportunity rows, query/page overlap, and evidence-ranked click declines. Quick-win candidates are observed query/page rows selected by aggregate Search Console average position, not proof of a stable current rank, and CTR is context rather than an eligibility filter. Cannibalization candidate totals/shares are scoped to observed query/page rows rather than true query-level property totals; content-decay results compare only pages returned in both periods and do not turn one-sided row absence into zero traffic. |
|
|
86
88
|
| **`indexing.list_pages`** / **`analytics.compare`** | Read | Analyze pages receiving Search Console impressions and compare two periods, optionally using the same search type and query/page/country/device/search-appearance filters for both periods. `analytics.compare` compares only dimension keys returned in both period responses; a key absent from one non-exhaustive Search Analytics response is not assumed to have zero metrics. Because its comparison contract includes average position, it excludes Discover and Google News. `indexing.list_pages` is performance data, not index coverage: a missing URL may still be indexed; use `urls.inspect` / `urls.inspect_many` for URL-level index status. |
|
|
87
89
|
| **`reports.weekly_digest`** | Read | Generate a plain-language seven-day performance report with movers, top pages, and one recommended action. Query movers compare only rows returned in both weekly Search Analytics result sets; a query missing from one bounded response is not treated as zero because Google does not guarantee every data row. |
|
|
88
|
-
| **`sites.add`** / **`sites.delete`** | Write | Add or remove a Search Console property. |
|
|
90
|
+
| **`sites.add`** / **`sites.delete`** | Write | Add or remove a Search Console property. `sites.add` only adds the property to the connected account's Search Console site set; it returns `ownership_verification_performed: false` because ownership verification is a separate Google Site Verification/Search Console workflow. |
|
|
89
91
|
| **`sitemaps.submit`** / **`sitemaps.delete`** | Write | Submit or remove a sitemap. |
|
|
90
92
|
| **`indexing.request`** | Write | Requests indexing through Google's Indexing API. Google currently restricts this API to pages containing `JobPosting` structured data or livestream pages containing `BroadcastEvent` inside `VideoObject`. It is not available for general webpage submission. |
|
|
91
93
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -210,6 +210,9 @@ const SITE_DETAIL_OUTPUT_SCHEMA = {
|
|
|
210
210
|
site: z.object(SITE_OUTPUT_SCHEMA),
|
|
211
211
|
};
|
|
212
212
|
|
|
213
|
+
const SITE_ADD_VERIFICATION_NOTE =
|
|
214
|
+
"Google Search Console Sites.add only adds the property to the user's Search Console site set; it does not verify ownership. Ownership verification is a separate Google Site Verification/Search Console workflow.";
|
|
215
|
+
|
|
213
216
|
const URL_INSPECTION_SCOPE_NOTE =
|
|
214
217
|
"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.";
|
|
215
218
|
|
|
@@ -241,6 +244,12 @@ const SEARCH_ANALYTICS_OUTPUT_SCHEMA = {
|
|
|
241
244
|
row_count: z.number().int().nonnegative(),
|
|
242
245
|
start_row: z.number().int().nonnegative(),
|
|
243
246
|
rows: z.array(z.object(SEARCH_ROW_OUTPUT_SCHEMA)),
|
|
247
|
+
data_state: z.enum(['all', 'final', 'hourly_all']).describe(
|
|
248
|
+
'The Search Analytics dataState requested from Google for this response.',
|
|
249
|
+
),
|
|
250
|
+
preliminary_data_possible: z.boolean().describe(
|
|
251
|
+
'True when the requested data state can include fresh data that is still being collected and processed. This does not assert that every returned row is incomplete.',
|
|
252
|
+
),
|
|
244
253
|
position_supported: z.boolean().describe(
|
|
245
254
|
'Whether average position is supported for the selected Search Analytics search type. False for Google Discover and Google News.',
|
|
246
255
|
),
|
|
@@ -279,6 +288,12 @@ const MESSAGE_OUTPUT_SCHEMA = {
|
|
|
279
288
|
message: z.string(),
|
|
280
289
|
};
|
|
281
290
|
|
|
291
|
+
const SITE_ADD_OUTPUT_SCHEMA = {
|
|
292
|
+
message: z.string(),
|
|
293
|
+
ownership_verification_performed: z.literal(false),
|
|
294
|
+
ownership_verification_note: z.string(),
|
|
295
|
+
};
|
|
296
|
+
|
|
282
297
|
const SEARCH_ANALYTICS_PAGINATION_OUTPUT_SCHEMA = z.object({
|
|
283
298
|
rows_fetched: z.number().int().nonnegative(),
|
|
284
299
|
pages_fetched: z.number().int().positive(),
|
|
@@ -573,7 +588,7 @@ const TOOL_CATALOG = [
|
|
|
573
588
|
{
|
|
574
589
|
name: 'sites.add',
|
|
575
590
|
description:
|
|
576
|
-
'Add a
|
|
591
|
+
'Add a website property to the connected Google account\'s Search Console site set. This does not verify ownership; Google handles ownership verification through a separate Site Verification/Search Console workflow.',
|
|
577
592
|
},
|
|
578
593
|
{
|
|
579
594
|
name: 'sites.delete',
|
|
@@ -980,10 +995,13 @@ class GscMcpRuntime {
|
|
|
980
995
|
' matching dimension. Expect the returned rows to cover only a',
|
|
981
996
|
' subset of total impressions; this is normal Google behavior, not',
|
|
982
997
|
' a data error.',
|
|
983
|
-
'- DATA FRESHNESS:
|
|
984
|
-
'
|
|
985
|
-
'
|
|
986
|
-
'
|
|
998
|
+
'- DATA FRESHNESS: data_state "all" can include fresh preliminary',
|
|
999
|
+
' data instead of imposing the finalized-data lag; "final" returns',
|
|
1000
|
+
' only finalized data and can therefore lag recent activity. When',
|
|
1001
|
+
' Google returns metadata.first_incomplete_date or',
|
|
1002
|
+
' metadata.first_incomplete_hour for date/hour groupings, treat',
|
|
1003
|
+
' that boundary and later rows as still subject to change. The',
|
|
1004
|
+
' response also echoes data_state and preliminary_data_possible.',
|
|
987
1005
|
'- AVERAGE POSITION is impression-weighted. To compute an overall',
|
|
988
1006
|
' position across multiple rows, use',
|
|
989
1007
|
' sum(position * impressions) / sum(impressions). Never plain-average',
|
|
@@ -1036,7 +1054,7 @@ class GscMcpRuntime {
|
|
|
1036
1054
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
1037
1055
|
start_date: SEARCH_CONSOLE_DATE_SCHEMA.describe('Start date (inclusive) in YYYY-MM-DD format.'),
|
|
1038
1056
|
end_date: SEARCH_CONSOLE_DATE_SCHEMA.describe(
|
|
1039
|
-
'End date (inclusive) in YYYY-MM-DD format.
|
|
1057
|
+
'End date (inclusive) in YYYY-MM-DD format. Recent dates are allowed: data_state all/hourly_all can include preliminary data, while final returns only finalized data.',
|
|
1040
1058
|
),
|
|
1041
1059
|
dimensions: z
|
|
1042
1060
|
.array(
|
|
@@ -1164,6 +1182,8 @@ class GscMcpRuntime {
|
|
|
1164
1182
|
row_count: bounded.items.length,
|
|
1165
1183
|
start_row,
|
|
1166
1184
|
rows: bounded.items,
|
|
1185
|
+
data_state,
|
|
1186
|
+
preliminary_data_possible: data_state !== 'final',
|
|
1167
1187
|
position_supported:
|
|
1168
1188
|
search_type !== 'discover' && search_type !== 'googleNews',
|
|
1169
1189
|
...(search_type === 'discover' || search_type === 'googleNews'
|
|
@@ -1356,11 +1376,11 @@ class GscMcpRuntime {
|
|
|
1356
1376
|
'sites.add',
|
|
1357
1377
|
{
|
|
1358
1378
|
title: 'Add Search Console property',
|
|
1359
|
-
description: 'Add a
|
|
1379
|
+
description: 'Add a website property to the connected Google account\'s Search Console site set. This does not verify ownership. Domain properties require an sc-domain prefix (e.g., sc-domain:example.com); URL-prefix properties require a full URL (e.g., https://example.com/).',
|
|
1360
1380
|
inputSchema: {
|
|
1361
1381
|
site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
|
|
1362
1382
|
},
|
|
1363
|
-
outputSchema:
|
|
1383
|
+
outputSchema: SITE_ADD_OUTPUT_SCHEMA,
|
|
1364
1384
|
annotations: WRITE_TOOL_ANNOTATIONS['sites.add'],
|
|
1365
1385
|
},
|
|
1366
1386
|
async ({ site_url }) => {
|
|
@@ -1370,7 +1390,12 @@ class GscMcpRuntime {
|
|
|
1370
1390
|
const accessToken = await this.getAccessToken(googleId);
|
|
1371
1391
|
await addSite(accessToken, site_url);
|
|
1372
1392
|
const message = `Successfully added site property: ${site_url}`;
|
|
1373
|
-
|
|
1393
|
+
const payload = {
|
|
1394
|
+
message,
|
|
1395
|
+
ownership_verification_performed: false as const,
|
|
1396
|
+
ownership_verification_note: SITE_ADD_VERIFICATION_NOTE,
|
|
1397
|
+
};
|
|
1398
|
+
return toolResponse(`${message}\n\n${SITE_ADD_VERIFICATION_NOTE}`, payload);
|
|
1374
1399
|
},
|
|
1375
1400
|
);
|
|
1376
1401
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP hints for each mutation exposed by this server.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* MCP defines destructiveHint=false for non-read-only tools that perform only
|
|
5
|
+
* additive updates. Search Console's site addition and sitemap submission are
|
|
6
|
+
* additive PUT operations; the corresponding DELETE operations remain
|
|
7
|
+
* destructive. Indexing publishes a URL_UPDATED notification with POST; each
|
|
8
|
+
* call can consume quota and change Google's indexing state, so it remains
|
|
9
|
+
* conservative (destructive + non-idempotent). Tool annotations are client
|
|
10
|
+
* hints, not an enforcement or confirmation mechanism.
|
|
10
11
|
*/
|
|
11
12
|
export const WRITE_TOOL_ANNOTATIONS = {
|
|
12
13
|
'sites.add': {
|
|
13
14
|
readOnlyHint: false,
|
|
14
|
-
destructiveHint:
|
|
15
|
+
destructiveHint: false,
|
|
15
16
|
idempotentHint: true,
|
|
16
17
|
openWorldHint: true,
|
|
17
18
|
},
|
|
@@ -23,7 +24,7 @@ export const WRITE_TOOL_ANNOTATIONS = {
|
|
|
23
24
|
},
|
|
24
25
|
'sitemaps.submit': {
|
|
25
26
|
readOnlyHint: false,
|
|
26
|
-
destructiveHint:
|
|
27
|
+
destructiveHint: false,
|
|
27
28
|
idempotentHint: true,
|
|
28
29
|
openWorldHint: true,
|
|
29
30
|
},
|