@digestseo/mcp-gsc 0.3.1 → 0.4.1
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/.dev.vars.example +12 -8
- package/CHANGELOG.md +94 -40
- package/LICENSE +21 -21
- package/README.md +115 -98
- package/SECURITY.md +73 -36
- package/SETUP.md +217 -199
- package/package.json +27 -13
- package/scripts/glama-start.mjs +14 -3
- package/src/access-mode.ts +59 -0
- package/src/access-token-lifecycle.ts +107 -0
- package/src/cannibalization-schema.ts +15 -0
- package/src/content-decay-schema.ts +8 -0
- package/src/crypto.ts +80 -58
- package/src/date-validation.ts +84 -0
- package/src/digest.ts +668 -648
- package/src/entrypoint.ts +39 -0
- package/src/google.ts +1334 -716
- package/src/index.ts +1667 -913
- package/src/indexed-pages-range.ts +54 -0
- package/src/indexing-property-authorization.ts +119 -0
- package/src/pending-auth-state.ts +87 -0
- package/src/quick-wins-schema.ts +44 -0
- package/src/result-bounds.ts +91 -0
- package/src/storage.ts +65 -98
- package/src/tool-rate-limit.ts +231 -0
- package/src/tool-rate-limiter-do.ts +23 -0
- package/src/write-tool-annotations.ts +43 -0
- package/wrangler.example.jsonc +54 -35
package/.dev.vars.example
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
# Local development secrets for `wrangler dev`.
|
|
2
|
-
# Copy this file to `.dev.vars` (gitignored) and fill in your own values.
|
|
3
|
-
# In production
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
# Local development secrets for `wrangler dev`.
|
|
2
|
+
# Copy this file to `.dev.vars` (gitignored) and fill in your own values.
|
|
3
|
+
# In production, set secrets with `wrangler secret put <NAME>` and configure
|
|
4
|
+
# GSC_ACCESS_MODE in wrangler.jsonc.
|
|
5
|
+
# See SETUP.md for how to obtain each value.
|
|
6
|
+
|
|
7
|
+
GOOGLE_CLIENT_ID=
|
|
8
|
+
GOOGLE_CLIENT_SECRET=
|
|
9
|
+
TOKEN_ENCRYPTION_KEY=
|
|
10
|
+
# Optional: defaults to readwrite. Set readonly to omit mutation tools and
|
|
11
|
+
# request only the Google Search Console read-only scope during OAuth.
|
|
12
|
+
GSC_ACCESS_MODE=readwrite
|
package/CHANGELOG.md
CHANGED
|
@@ -1,40 +1,94 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.1] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Large Search Analytics, comparison, cannibalization, quick-win, content-decay, page/query drilldown, and impression-page proxy responses now use deterministic bounded output with explicit pagination/truncation metadata instead of risking oversized MCP structured content.
|
|
14
|
+
- Content-decay assessment now requires meaningful click evidence plus supporting impression or average-position deterioration for a `likely_decay` classification, while separately identifying weak evidence and improving visibility with click volatility.
|
|
15
|
+
- Weekly-digest recommendations now keep measured Search Console observations separate from causal hypotheses and avoid unsupported publishing or indexing-speed prescriptions.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- True site-total `analytics.query` calls with `dimensions: []` now accept Google's valid aggregate rows when the `keys` field is omitted.
|
|
19
|
+
- Query/page drilldowns now expose usable paging inputs instead of reporting pagination metadata that callers could not advance.
|
|
20
|
+
- `indexing.list_pages` now makes its impressions-based proxy semantics explicit and safely pages large result sets.
|
|
21
|
+
|
|
22
|
+
## [0.4.0] - 2026-09-21
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Bounded bulk URL inspection with \`urls.inspect_many\`, preserving input order and per-URL failures.
|
|
26
|
+
- Exact property lookup with \`sites.get\`.
|
|
27
|
+
- Query/page drilldowns with \`insights.page_queries\` and \`insights.query_pages\`.
|
|
28
|
+
- Hourly Search Analytics support and News Showcase panel aggregation.
|
|
29
|
+
- Read-only deployment mode for least-privilege autonomous/agent installations.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Migrated the remote MCP runtime to Cloudflare's current stateless handler and modernized OAuth discovery with Client ID Metadata Documents, S256 PKCE, reconnect isolation, and Durable Object-backed pending authorization state.
|
|
33
|
+
- Self-host npm transport metadata now accurately describes the loopback Streamable HTTP launcher and OAuth callback.
|
|
34
|
+
- Search Console calendar defaults now follow Pacific Time and weekly reports default to usually-complete data.
|
|
35
|
+
- Weekly-digest guidance now avoids unsupported indexing, zero-click, branded-query, and average-position conclusions and points users to the underlying Search Console evidence.
|
|
36
|
+
- Search Analytics requests now reject documented invalid cross-field combinations before reaching Google.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Higher-level Search Analytics pagination, comparison zero-baseline math, indexing authorization/eligibility bounds, OAuth state atomicity, access-token lifecycle behavior, and tool rate-limit RPC behavior.
|
|
40
|
+
- Dependency and CI hygiene, including current Cloudflare Vitest integration, supported Wrangler floor, Node 24 GitHub Actions, and Ubuntu 26 runners.
|
|
41
|
+
|
|
42
|
+
## [0.3.1] - 2026-07-27
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- Namespaced MCP tool names and typed output schemas for more consistent client integration.
|
|
46
|
+
- Distribution surfaces for self-hosting: a portable launcher, Docker/Glama support, and Cursor and Claude Code plugin metadata.
|
|
47
|
+
- The public npm package, `@digestseo/mcp-gsc`, with an `npx` launcher.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Registry, manifest, and package metadata now point to the official DigestSEO listing.
|
|
51
|
+
|
|
52
|
+
## [0.3.0] - 2026-07-13
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- Search Console write operations for properties and sitemaps, Indexing API requests, and analytics insight tools for quick wins, cannibalization, content decay, indexed pages, and period comparisons.
|
|
56
|
+
- An on-demand weekly Search Console performance digest with top pages, movers, and recommended actions.
|
|
57
|
+
- Static tool and connection metadata for registry discovery.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- Google OAuth permissions and MCP annotations now distinguish read operations from the available write operations.
|
|
61
|
+
|
|
62
|
+
## [0.2.0] - 2026-06-10
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- `get_capabilities` tool — returns the tool catalog and a non-destructive auth-status probe for better first-run discovery in lazy-loading clients.
|
|
66
|
+
- **Pagination** for `query_search_analytics`: new `start_row` parameter (Google's `startRow`), plus a `next_start_row` field in the response whenever a full page came back.
|
|
67
|
+
- **Parameter descriptions on every tool input** — including the `sc-domain:` vs URL-prefix property format trap, the YYYY-MM-DD date format, and the 2–3 day GSC data lag — so MCP clients make correct calls on the first try.
|
|
68
|
+
- **MCP tool annotations**: all five tools now declare `readOnlyHint: true` and human-readable titles.
|
|
69
|
+
- **Offline unit tests** (`npm run test:unit`, 15 tests): crypto roundtrip/tamper checks, OAuth URL construction, refresh-token error mapping, and Search Console request encoding — no deployment needed.
|
|
70
|
+
- **GitHub Actions CI**: typecheck + unit tests on every push and pull request.
|
|
71
|
+
- `SECURITY.md` with a private-reporting channel and a full data-handling inventory.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- `query_search_analytics` now returns compact JSON shaped as `{ row_count, start_row, rows, next_start_row? }` instead of a pretty-printed bare array — roughly 3× fewer tokens for the same data.
|
|
75
|
+
- Default `row_limit` lowered from 1000 to **100**. Most conversational questions need far fewer rows; bulk consumers can still request up to 25,000 and paginate.
|
|
76
|
+
- Input validation tightened: dates must be `YYYY-MM-DD`, `row_limit` must be an integer in 1–25000.
|
|
77
|
+
- Error messages no longer hardcode Claude.ai — they now say "your MCP client (e.g. Claude.ai → Settings → Connectors)".
|
|
78
|
+
- The version reported by the MCP server and `get_capabilities` is now read from `package.json` (single source of truth).
|
|
79
|
+
- A failure inside the OAuth provider's `completeAuthorization` now returns a clean 500 instead of an unhandled exception.
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
- Server name: the MCP server now identifies as `mcp-gsc` (in the `McpServer` name and the `GET /` response body) to match the repo, package, and worker name — it was previously `hosted-gsc-mcp`.
|
|
83
|
+
- A transient Google error no longer deletes a user's stored credentials. `refreshAccessToken` now treats only a definitive `invalid_grant` as a revocation; other non-OK responses (bare 400/401, 5xx, network) propagate as plain errors, so a temporary blip can no longer trigger `deleteUser`.
|
|
84
|
+
|
|
85
|
+
## [0.1.0] - 2026-06-02
|
|
86
|
+
|
|
87
|
+
Initial public release — a hosted MCP server for Google Search Console, self-hostable on Cloudflare Workers.
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
- One-click Google OAuth onboarding (`/authorize` → Google → `/google/callback`): connect with your Google account, no API keys to copy.
|
|
91
|
+
- Four read-only tools: `list_sites`, `query_search_analytics`, `inspect_url`, `list_sitemaps`.
|
|
92
|
+
- Bring-your-own Google OAuth credentials and Cloudflare KV — see [SETUP.md](SETUP.md).
|
|
93
|
+
- AES-GCM encryption of stored Google refresh tokens (`src/crypto.ts`).
|
|
94
|
+
- Structural smoke tests (`npm run test:smoke`).
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DigestSEO
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DigestSEO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
# mcp-gsc
|
|
2
|
-
|
|
1
|
+
# mcp-gsc
|
|
2
|
+
|
|
3
3
|
<!-- Logo asset for marketplace submissions: assets/logo-400.png (400x400). -->
|
|
4
|
-
|
|
5
|
-
[](LICENSE)
|
|
6
|
-
[](https://modelcontextprotocol.io)
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
7
|
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.AKzar1el/mcp-gsc)
|
|
8
8
|
[](https://github.com/AKzar1el/mcp-gsc/actions/workflows/ci.yml)
|
|
9
9
|
[](https://www.npmjs.com/package/@digestseo/mcp-gsc)
|
|
10
|
-
[](https://glama.ai/mcp/servers/AKzar1el/mcp-gsc)
|
|
11
11
|
|
|
12
12
|
Part of the **[DigestSEO](https://digestseo.com/)** suite of open-source SEO tools.
|
|
13
13
|
|
|
14
14
|
- Product: [digestseo.com/gsc-mcp](https://digestseo.com/gsc-mcp/)
|
|
15
|
+
- Engineering case study: [DigestSEO MCP Suite — AI visibility, Search Console, web validation, and trend intelligence](https://tomiseregi.si/projects/digestseo-mcp-suite)
|
|
15
16
|
- Support: [digestseo.com/support](https://digestseo.com/support/)
|
|
16
17
|
- Privacy: [digestseo.com/privacy](https://digestseo.com/privacy/)
|
|
17
18
|
|
|
@@ -21,10 +22,12 @@ The self-hosting launcher is available as [`@digestseo/mcp-gsc`](https://www.npm
|
|
|
21
22
|
npx -y @digestseo/mcp-gsc
|
|
22
23
|
```
|
|
23
24
|
|
|
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. 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
|
+
|
|
24
27
|
For Google OAuth and Cloudflare deployment configuration, follow [SETUP.md](SETUP.md).
|
|
25
28
|
|
|
26
29
|
### Connect in 30 seconds
|
|
27
|
-
|
|
30
|
+
|
|
28
31
|
Every deployed instance exposes the same endpoint shape:
|
|
29
32
|
|
|
30
33
|
```
|
|
@@ -38,102 +41,116 @@ https://mcp-gsc.digestseo.com/mcp
|
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
**Claude Code**
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
claude mcp add --transport http gsc https://<your-worker>.workers.dev/mcp
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Cursor**
|
|
47
|
-
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
claude mcp add --transport http gsc https://<your-worker>.workers.dev/mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Cursor**
|
|
50
|
+
|
|
48
51
|
[](https://cursor.com/en/install-mcp?name=gsc&config=eyJ1cmwiOiJodHRwczovL21jcC1nc2MuZGlnZXN0c2VvLmNvbS9tY3AifQ%3D%3D)
|
|
49
|
-
|
|
50
|
-
**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
|
|
53
|
+
**Kiro**
|
|
54
|
+
|
|
55
|
+
[](https://kiro.dev/launch/mcp/add?name=mcp-gsc&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp-gsc.digestseo.com%2Fmcp%22%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D)
|
|
56
|
+
|
|
57
|
+
Uses the hosted endpoint above; complete its Google OAuth flow on first use.
|
|
58
|
+
|
|
59
|
+
**ChatGPT** — add it as a custom MCP app in developer mode; see [Connect in your AI client](#connect-in-your-ai-client) below.
|
|
60
|
+
|
|
61
|
+
A self-hostable [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **Google Search Console**. Connect it to Claude.ai, Cursor, ChatGPT, or any MCP-compatible client and ask your AI assistant about your site's organic search performance — impressions, clicks, top queries, index status, and sitemap health — straight from your own Google account.
|
|
62
|
+
|
|
63
|
+
It runs on [Cloudflare Workers](https://workers.cloudflare.com/) and ships with one-click Google OAuth onboarding: connect the server in your client, sign in with Google once, grant the requested Google Search Console permissions, and you're done. No API keys to copy around and no service-account JSON to manage.
|
|
64
|
+
|
|
65
|
+
> **Prefer zero setup?** The hosted version — with automatic weekly email digests delivered to your inbox — is at **[digestseo.com](https://digestseo.com)**. This repository is the open-source core you can run yourself.
|
|
66
|
+
|
|
67
|
+
## Tools
|
|
68
|
+
|
|
69
|
+
By default (`GSC_ACCESS_MODE=readwrite`), this server exposes 21 tools. Read-only analytics and reporting tools are marked with MCP's `readOnlyHint`; the write tools below can change Search Console properties, sitemaps, or indexing state. Set `GSC_ACCESS_MODE=readonly` to request only the Search Console read-only scope and expose the 16 read-only tools.
|
|
70
|
+
|
|
71
|
+
| Tool | Access | What it does |
|
|
72
|
+
|---|---|---|
|
|
64
73
|
| **`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. |
|
|
65
74
|
| **`sites.list`** | Read | List the Search Console properties the connected Google account can access (`siteUrl`, `permissionLevel`). |
|
|
66
|
-
| **`
|
|
75
|
+
| **`sites.get`** | Read | Retrieve one exact Search Console property and the connected account's permission level for it. |
|
|
76
|
+
| **`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`. |
|
|
77
|
+
| **`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. |
|
|
67
78
|
| **`urls.inspect`** | Read | Google's URL Inspection report for a single page. |
|
|
79
|
+
| **`urls.inspect_many`** | Read | Inspect up to 10 URLs sequentially in one call. Each URL still consumes one Google URL Inspection request and one unit of the server's shared inspection safety budget. |
|
|
68
80
|
| **`sitemaps.list`** / **`sitemaps.get`** | Read | List submitted sitemaps or retrieve one sitemap's details. |
|
|
69
|
-
| **`insights.quick_wins`** / **`insights.cannibalization`** / **`insights.content_decay`** | Read | Surface
|
|
70
|
-
| **`indexing.list_pages`** / **`analytics.compare`** | Read | Analyze pages receiving impressions and compare two periods. |
|
|
81
|
+
| **`insights.quick_wins`** / **`insights.cannibalization`** / **`insights.content_decay`** | Read | Surface striking-distance opportunities, query/page overlap, and evidence-ranked click declines. Content-decay results distinguish likely decay from weak evidence and improving visibility with click volatility. |
|
|
82
|
+
| **`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. `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. |
|
|
71
83
|
| **`reports.weekly_digest`** | Read | Generate a plain-language seven-day performance report with movers, top pages, and one recommended action. |
|
|
72
84
|
| **`sites.add`** / **`sites.delete`** | Write | Add or remove a Search Console property. |
|
|
73
85
|
| **`sitemaps.submit`** / **`sitemaps.delete`** | Write | Submit or remove a sitemap. |
|
|
74
|
-
| **`indexing.request`** | Write |
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- *"
|
|
87
|
-
- *"
|
|
88
|
-
- *"
|
|
89
|
-
- *"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
86
|
+
| **`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. |
|
|
87
|
+
|
|
88
|
+
Read-write mode requests the Google Search Console read-write and Indexing API scopes. Read-only mode requests only `https://www.googleapis.com/auth/webmasters.readonly` (plus `openid` and `email`) and does not register the five write tools. Read-write remains the default so existing deployments retain their current behavior; see [SETUP.md](SETUP.md) to select a mode before connecting users.
|
|
89
|
+
|
|
90
|
+
Large analytics responses are bounded at the MCP boundary instead of being generated and then discarded by clients with structured-content limits. List-style tools expose `start_row` / `next_start_row` where applicable; ranked analytical tools expose `limit`, `start_row`, and `result_page` metadata including `has_more`, `truncated`, and `byte_limit_reached`. Continue paging while `has_more` is true rather than assuming one response is complete.
|
|
91
|
+
|
|
92
|
+
> **`indexing.request` eligibility.** Google's Indexing API is not a general-purpose page submission tool — as of this writing, Google's own documentation limits it to two content types: pages with `JobPosting` structured data, and livestream pages with `BroadcastEvent` structured data nested inside `VideoObject`. Before submitting, the server fetches the target URL and checks its JSON-LD for one of those two types; if neither is present (or the page can't be fetched), it returns an error explaining why the URL is ineligible instead of calling the Indexing API. A successful submission is only an acknowledgment that Google received the notification — it does not guarantee the URL will be indexed.
|
|
93
|
+
|
|
94
|
+
## What you can ask
|
|
95
|
+
|
|
96
|
+
Once connected, ask your assistant things like:
|
|
97
|
+
|
|
98
|
+
- *"What are my top 20 queries by clicks in the last 28 days?"*
|
|
99
|
+
- *"Compare impressions for example.com this month vs last month — which pages dropped?"*
|
|
100
|
+
- *"Is `https://example.com/pricing` indexed? When was it last crawled?"*
|
|
101
|
+
- *"Which queries does my blog rank position 5–15 for? Those are my quick wins."*
|
|
102
|
+
- *"Give me a weekly digest for `sc-domain:example.com` ending today."*
|
|
103
|
+
- *"Do any of my sitemaps have errors or warnings?"*
|
|
104
|
+
- *"Split my clicks into brand vs non-brand using a regex on the query."*
|
|
105
|
+
- *"How is my site doing in Google Discover vs regular web search?"*
|
|
106
|
+
|
|
107
|
+
## Connect in your AI client
|
|
108
|
+
|
|
109
|
+
Once you've deployed the server (see **[SETUP.md](SETUP.md)**), connect it by pasting your Worker's `/mcp` URL into your client:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
https://<your-worker>.workers.dev/mcp
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Claude.ai / Claude Desktop** — go to **Customize → Connectors**, click **+ → Add custom connector**, enter a name and paste the `/mcp` URL. Leave the optional advanced OAuth Client ID/Secret fields blank. On Team/Enterprise, an Owner or Primary Owner must first add the custom Web connector from **Organization settings → Connectors**; members then connect it from Customize → Connectors. On first connection, Claude opens the Google sign-in flow.
|
|
116
|
+
- **Cursor** — add a remote **Streamable HTTP** MCP server pointing at the same `/mcp` URL; Cursor supports OAuth for remote HTTP MCP servers. The hosted Add to Cursor button above uses this endpoint directly.
|
|
117
|
+
- **ChatGPT** — enable **Developer mode**, then create a custom MCP **app** from **Settings → Apps → Create** (admins/owners can also use **Workspace settings → Apps → Create**). Provide the `/mcp` endpoint, select the applicable authentication option, **Scan Tools**, complete OAuth, then create the app. Full MCP including write/modify tools is currently available to Business and Enterprise/Edu; Pro custom MCP access is read/fetch-only, so use `GSC_ACCESS_MODE=readonly` for that path.
|
|
118
|
+
|
|
119
|
+
The `/mcp` endpoint is the same across clients, but each host has its own setup and permission flow.
|
|
120
|
+
|
|
121
|
+
## Setup
|
|
122
|
+
|
|
123
|
+
Self-hosting means bringing your own Google OAuth credentials and Cloudflare account. The full, copy-pasteable walkthrough is in **[SETUP.md](SETUP.md)** — including an important note about Google's OAuth verification and the 7-day refresh-token limit while your app is unverified.
|
|
124
|
+
|
|
125
|
+
Quick shape:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
git clone https://github.com/<you>/mcp-gsc.git
|
|
129
|
+
cd mcp-gsc
|
|
130
|
+
npm install
|
|
131
|
+
cp wrangler.example.jsonc wrangler.jsonc # then paste in your KV ids
|
|
132
|
+
# set GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / TOKEN_ENCRYPTION_KEY as secrets
|
|
133
|
+
# choose GSC_ACCESS_MODE=readonly in wrangler.jsonc for a read-only deployment
|
|
134
|
+
npm run deploy
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
See **[SETUP.md](SETUP.md)** for every step in detail.
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm install
|
|
143
|
+
npm test # typecheck + offline unit tests (what CI runs)
|
|
144
|
+
npm run dev # wrangler dev (local)
|
|
145
|
+
npm run test:smoke # structural smoke tests against a deployment
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
[MIT](LICENSE) © 2026 DigestSEO.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
*Built and maintained by [Tomi Šeregi](https://tomiseregi.si), the builder behind [digestseo.com](https://digestseo.com) — weekly SEO digests for non-technical site owners.*
|
package/SECURITY.md
CHANGED
|
@@ -1,36 +1,73 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Reporting a vulnerability
|
|
4
|
-
|
|
5
|
-
Please **do not open a public issue** for security problems. Instead, use
|
|
6
|
-
[GitHub's private vulnerability reporting](https://github.com/AKzar1el/mcp-gsc/security/advisories/new)
|
|
7
|
-
on this repository. You'll get a response within a few days.
|
|
8
|
-
|
|
9
|
-
## What this server stores and how
|
|
10
|
-
|
|
11
|
-
A deployed instance of `mcp-gsc` handles Google OAuth credentials on behalf of
|
|
12
|
-
its users. The full data inventory:
|
|
13
|
-
|
|
14
|
-
| Data | Where | Protection |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| Google **refresh token** (per user) | Workers KV (`USER_KV`) | Encrypted at rest with **AES-256-GCM** (`src/crypto.ts`) using the `TOKEN_ENCRYPTION_KEY` secret; a fresh random IV per encryption. |
|
|
17
|
-
| Google account **id and email** (per user) | Workers KV (`USER_KV`) | Stored in plaintext alongside the encrypted token, used only to key and label the record. |
|
|
18
|
-
| Pending OAuth state |
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Please **do not open a public issue** for security problems. Instead, use
|
|
6
|
+
[GitHub's private vulnerability reporting](https://github.com/AKzar1el/mcp-gsc/security/advisories/new)
|
|
7
|
+
on this repository. You'll get a response within a few days.
|
|
8
|
+
|
|
9
|
+
## What this server stores and how
|
|
10
|
+
|
|
11
|
+
A deployed instance of `mcp-gsc` handles Google OAuth credentials on behalf of
|
|
12
|
+
its users. The full data inventory:
|
|
13
|
+
|
|
14
|
+
| Data | Where | Protection |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Google **refresh token** (per user) | Workers KV (`USER_KV`) | Encrypted at rest with **AES-256-GCM** (`src/crypto.ts`) using the `TOKEN_ENCRYPTION_KEY` secret; a fresh random IV per encryption. |
|
|
17
|
+
| Google account **id and email** (per user) | Workers KV (`USER_KV`) | Stored in plaintext alongside the encrypted token, used only to key and label the record. |
|
|
18
|
+
| Pending OAuth state | Durable Object storage (`PendingAuthState`) | Stored per nonce; consumed through a strongly consistent transaction so exactly one callback succeeds; expires after 10 minutes. |
|
|
19
|
+
| Tool rate-limit state | Durable Object storage (`ToolRateLimiter`) | Stores a SHA-256-derived user bucket plus counter/window state; never stores tool arguments, URLs, tokens, or response data. |
|
|
20
|
+
| MCP client tokens | Workers KV (`OAUTH_KV`) | Managed by [`@cloudflare/workers-oauth-provider`](https://github.com/cloudflare/workers-oauth-provider). |
|
|
21
|
+
| Google **access tokens** | Worker isolate memory only | Cached per Google user id by `GoogleAccessTokenLifecycle`; never written to storage and discarded with the isolate. Tokens expire within an hour. |
|
|
22
|
+
|
|
23
|
+
Notes:
|
|
24
|
+
|
|
25
|
+
- Access is configured by `GSC_ACCESS_MODE`; the default is `readwrite` to
|
|
26
|
+
preserve existing deployments' behavior. In that mode the server requests
|
|
27
|
+
`openid`, `email`, `https://www.googleapis.com/auth/webmasters` (the
|
|
28
|
+
read-write Search Console scope), and
|
|
29
|
+
`https://www.googleapis.com/auth/indexing`. It registers write-capable
|
|
30
|
+
tools to add/delete properties, submit/delete sitemaps, and send eligible
|
|
31
|
+
Indexing API notifications.
|
|
32
|
+
- With `GSC_ACCESS_MODE=readonly`, the server requests only `openid`, `email`,
|
|
33
|
+
and `https://www.googleapis.com/auth/webmasters.readonly`. It does not
|
|
34
|
+
register `sites.add`, `sites.delete`, `sitemaps.submit`, `sitemaps.delete`,
|
|
35
|
+
or `indexing.request`; the remaining tool suite uses APIs that accept the
|
|
36
|
+
read-only Search Console scope.
|
|
37
|
+
- The Indexing API is requested only in read-write mode for `indexing.request`.
|
|
38
|
+
Google restricts that API to `JobPosting` pages or livestream pages with a
|
|
39
|
+
`BroadcastEvent` inside a `VideoObject`; it is not a general page-submission
|
|
40
|
+
API.
|
|
41
|
+
- Secrets (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `TOKEN_ENCRYPTION_KEY`)
|
|
42
|
+
are read from Worker secrets / `.dev.vars`, never from the repository.
|
|
43
|
+
- When Google reports a refresh token as revoked (`invalid_grant`), the stored
|
|
44
|
+
user record is deleted immediately.
|
|
45
|
+
- MCP requests use Cloudflare's stateless MCP handler. The historical
|
|
46
|
+
`GscMcpAgent` Durable Object remains only as an unrouted compatibility shell;
|
|
47
|
+
it does not hold Google access-token cache state.
|
|
48
|
+
- Search Console data is fetched on demand and returned to the MCP client;
|
|
49
|
+
it is never persisted by this server.
|
|
50
|
+
|
|
51
|
+
## Tool rate limits
|
|
52
|
+
|
|
53
|
+
The server enforces the following fixed-window limits before making upstream
|
|
54
|
+
requests. They are per authenticated user and tool category; `indexing.request`
|
|
55
|
+
also has a deployment-wide project guard. A rejected invocation returns a retry
|
|
56
|
+
delay and does not disclose another user's usage.
|
|
57
|
+
|
|
58
|
+
| Category | Tools | Limit |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Search Analytics | `analytics.query`, `insights.quick_wins`, `insights.cannibalization`, `insights.content_decay`, `indexing.list_pages`, `analytics.compare` | 30 per user / 10 minutes |
|
|
61
|
+
| URL inspection | `urls.inspect`, `urls.inspect_many` | 20 inspected URLs per user / 24 hours; batch requests reserve one unit per URL |
|
|
62
|
+
| Search Console writes | `sites.add`, `sites.delete`, `sitemaps.submit`, `sitemaps.delete` | 10 per user / hour |
|
|
63
|
+
| Indexing publish | `indexing.request` | 2 per user / 24 hours; 200 per deployment / 24 hours |
|
|
64
|
+
| Weekly digest | `reports.weekly_digest` | 6 per user / hour |
|
|
65
|
+
|
|
66
|
+
The Indexing API's default publish quota is 200 requests per project per day.
|
|
67
|
+
Search Analytics and URL Inspection have separate load and per-site quotas, so
|
|
68
|
+
these limits are intentionally conservative rather than a replacement for
|
|
69
|
+
monitoring the Google Cloud project quota.
|
|
70
|
+
|
|
71
|
+
## Supported versions
|
|
72
|
+
|
|
73
|
+
Only the latest release on `main` is supported with security fixes.
|