@digestseo/mcp-gsc 0.4.25 → 0.4.26
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 +6 -0
- package/README.md +1 -1
- package/SETUP.md +2 -0
- package/package.json +3 -2
- package/scripts/glama-start.mjs +13 -0
- package/src/google.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.26] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- The published `npx` launcher now rejects malformed `TOKEN_ENCRYPTION_KEY` values before starting Wrangler, requiring valid base64 that decodes to exactly the documented 32-byte AES-256 key without logging the secret value.
|
|
14
|
+
- Higher-level Search Analytics pagination now follows Google's documented empty-page termination contract instead of stopping on any short non-empty page, preventing premature truncation when another provider page is still available.
|
|
15
|
+
|
|
10
16
|
## [0.4.25] - 2026-09-23
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ The self-hosting launcher is available as [`@digestseo/mcp-gsc`](https://www.npm
|
|
|
22
22
|
npx -y @digestseo/mcp-gsc
|
|
23
23
|
```
|
|
24
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. 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). The launcher exits before starting Wrangler if `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, or `TOKEN_ENCRYPTION_KEY` is missing or blank,
|
|
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). The launcher exits before starting Wrangler if `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, or `TOKEN_ENCRYPTION_KEY` is missing or blank, and it also rejects a nonblank `TOKEN_ENCRYPTION_KEY` unless it is valid base64 that decodes to exactly 32 bytes. Errors identify only the invalid or missing setting, never its secret value, so a broken OAuth session is not started. It keeps secret values in the child process environment and Wrangler loads them through declared local secrets; their values are not copied into Wrangler `--var` command-line arguments. 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
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
28
|
|
package/SETUP.md
CHANGED
|
@@ -111,6 +111,8 @@ openssl rand -base64 32
|
|
|
111
111
|
|
|
112
112
|
Paste that value when prompted. Keep it safe — if you lose it, the refresh tokens already in KV become undecryptable and every user has to reconnect.
|
|
113
113
|
|
|
114
|
+
The published `npx` launcher validates this exact key format before starting Wrangler. A malformed base64 value or a value that does not decode to exactly 32 bytes is rejected without printing the secret value.
|
|
115
|
+
|
|
114
116
|
For **local development**, put the same three values in `.dev.vars` instead — copy `.dev.vars.example` to `.dev.vars` (it's gitignored). The published npm launcher can also read them from the process environment; its bundled Wrangler config declares `GOOGLE_CLIENT_SECRET` and `TOKEN_ENCRYPTION_KEY` as required local secrets so those sensitive values do not need to be serialized into `--var` command-line arguments:
|
|
115
117
|
|
|
116
118
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digestseo/mcp-gsc",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "scripts/glama-start.mjs",
|
|
6
6
|
"bin": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"build": "npm run typecheck",
|
|
51
51
|
"start": "node scripts/glama-start.mjs",
|
|
52
52
|
"typecheck": "tsc --noEmit",
|
|
53
|
+
"test:launcher": "node --test tests/launcher.test.mjs",
|
|
53
54
|
"test:oauth-state": "tsx --test tests/pending-auth-state.test.ts",
|
|
54
55
|
"test:comparison": "tsx --test tests/performance-comparison.test.ts",
|
|
55
56
|
"test:annotations": "tsx --test tests/write-tool-annotations.test.ts",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"test:indexing-eligibility": "tsx --test tests/indexing-eligibility.test.ts",
|
|
62
63
|
"test:insight-pagination": "tsx --test tests/insight-pagination.test.ts",
|
|
63
64
|
"test:tool-rate-limits": "tsx --test tests/tool-rate-limit.test.ts",
|
|
64
|
-
"test": "npm run typecheck && npm run test:metadata && npm run test:oauth-state && npm run test:comparison && npm run test:annotations && npm run test:worker && npm run test:access-mode && npm run test:access-token-lifecycle && npm run test:analytics-response-metadata && npm run test:indexing-authorization && npm run test:indexing-eligibility && npm run test:insight-pagination && npm run test:tool-rate-limits && npm run test:unit",
|
|
65
|
+
"test": "npm run typecheck && npm run test:metadata && npm run test:launcher && npm run test:oauth-state && npm run test:comparison && npm run test:annotations && npm run test:worker && npm run test:access-mode && npm run test:access-token-lifecycle && npm run test:analytics-response-metadata && npm run test:indexing-authorization && npm run test:indexing-eligibility && npm run test:insight-pagination && npm run test:tool-rate-limits && npm run test:unit",
|
|
65
66
|
"test:metadata": "tsx --test tests/metadata.test.ts",
|
|
66
67
|
"test:unit": "tsx --test tests/unit.test.ts",
|
|
67
68
|
"test:smoke": "node --test tests/smoke.test.mjs",
|
package/scripts/glama-start.mjs
CHANGED
|
@@ -19,6 +19,19 @@ if (missingRequiredEnvironmentVariables.length > 0) {
|
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const tokenEncryptionKey = process.env.TOKEN_ENCRYPTION_KEY;
|
|
23
|
+
const tokenEncryptionKeyPattern =
|
|
24
|
+
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
25
|
+
if (
|
|
26
|
+
!tokenEncryptionKeyPattern.test(tokenEncryptionKey)
|
|
27
|
+
|| Buffer.from(tokenEncryptionKey, 'base64').byteLength !== 32
|
|
28
|
+
) {
|
|
29
|
+
console.error(
|
|
30
|
+
'[mcp-gsc] Invalid TOKEN_ENCRYPTION_KEY: expected a valid base64-encoded 32-byte (256-bit) AES key. See SETUP.md for local launcher configuration.',
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
const port = process.env.PORT || '8080';
|
|
23
36
|
const localBaseUrl = `http://127.0.0.1:${port}`;
|
|
24
37
|
const mcpUrl = `${localBaseUrl}/mcp`;
|
package/src/google.ts
CHANGED
|
@@ -652,6 +652,8 @@ export async function querySearchAnalytics(
|
|
|
652
652
|
|
|
653
653
|
/**
|
|
654
654
|
* Fetches Search Analytics rows in 25,000-row pages for higher-level tools.
|
|
655
|
+
* Google's documented pagination contract uses an explicit empty response as
|
|
656
|
+
* the terminal page, so a short non-empty page is not treated as exhaustion.
|
|
655
657
|
* A full final page conservatively reports localLimitReached because the API
|
|
656
658
|
* can have more rows even when Search Console's own internal limits apply.
|
|
657
659
|
*/
|
|
@@ -685,14 +687,14 @@ export async function querySearchAnalyticsPaginated(
|
|
|
685
687
|
|
|
686
688
|
const acceptedRows = page.rows.slice(0, requestedRows);
|
|
687
689
|
rows.push(...acceptedRows);
|
|
688
|
-
if (page.rows.length
|
|
690
|
+
if (page.rows.length === 0) {
|
|
689
691
|
return { rows, pagesFetched, localLimitReached: false };
|
|
690
692
|
}
|
|
691
693
|
if (rows.length >= maximumRows || page.rows.length > requestedRows) {
|
|
692
694
|
return { rows, pagesFetched, localLimitReached: true };
|
|
693
695
|
}
|
|
694
696
|
|
|
695
|
-
startRow +=
|
|
697
|
+
startRow += requestedRows;
|
|
696
698
|
}
|
|
697
699
|
|
|
698
700
|
return { rows, pagesFetched, localLimitReached: true };
|