@digestseo/mcp-gsc 0.4.34 → 0.4.35

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 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.35] - 2026-09-24
11
+
12
+ ### Fixed
13
+ - Search Analytics pagination now treats provider responses shorter than the requested page size as terminal, matching Google's documented contract and avoiding false continuations or unnecessary follow-up requests across direct and higher-level workflows.
14
+ - `insights.page_queries` now rejects exact page URLs outside the supplied Search Console property before reserving local Search Analytics safety budget, resolving Google credentials, or calling the provider, preventing misleading empty cross-property drilldowns.
15
+
10
16
  ## [0.4.34] - 2026-09-24
11
17
 
12
18
  ### Fixed
package/README.md CHANGED
@@ -66,6 +66,8 @@ Codex CLI and the Codex IDE extension share MCP configuration, so registering th
66
66
 
67
67
  When this repository is installed as a Cursor plugin, Cursor also auto-discovers three bundled Agent Skills that compose the MCP tools into evidence-safe workflows: `gsc-weekly-review`, `gsc-indexing-triage`, and `gsc-search-opportunities`. The **Add to Cursor** button above installs the MCP connection only; the bundled skills are part of the repository's Cursor plugin package.
68
68
 
69
+ The button and plugin MCP configuration above are for Cursor running on the same machine as the npm launcher. **Cursor Cloud Agents run in isolated cloud VMs, so they cannot reach `127.0.0.1` on your computer.** For Cloud Agents, deploy your own externally reachable HTTPS Worker and add its `/mcp` URL from the MCP controls at [cursor.com/agents](https://cursor.com/agents); Cursor supports HTTP MCP servers and OAuth there. Do not reuse the loopback URL for a Cloud Agent.
70
+
69
71
  **GitHub Copilot CLI**
70
72
 
71
73
  ```bash
@@ -89,7 +91,9 @@ The Gemini CLI extension bundles the verified local loopback MCP configuration p
89
91
 
90
92
  Start the npm launcher before using either install button or the Copilot plugin. Complete its Google OAuth flow on first use.
91
93
 
92
- **ChatGPT** — ChatGPT cannot connect directly to this loopback `127.0.0.1` endpoint. Use OpenAI's **Secure MCP Tunnel** for the local launcher, or deploy your own remote Cloudflare Worker and add that `/mcp` URL as a custom MCP app in Developer mode; see [Connect in your AI client](#connect-in-your-ai-client) below.
94
+ **ChatGPT** — ChatGPT cannot connect directly to this loopback `127.0.0.1` endpoint. For private/local use, create an OpenAI **Secure MCP Tunnel**, then run `tunnel-client` on the same machine as the launcher so it can reach `http://127.0.0.1:8080/mcp`; when creating the ChatGPT app, choose the **Tunnel** connection and select that tunnel. The tunnel setup requires an OpenAI `tunnel_id` and runtime API key; see OpenAI's [Secure MCP Tunnel guide](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels). The tunnel keeps the MCP server private and is intended for private/developer-mode connectivity, not public plugin distribution.
95
+
96
+ Alternatively, deploy your own remote Cloudflare Worker and add its HTTPS `/mcp` URL as a custom MCP app in Developer mode; see [Connect in your AI client](#connect-in-your-ai-client) below.
93
97
 
94
98
  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.
95
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digestseo/mcp-gsc",
3
- "version": "0.4.34",
3
+ "version": "0.4.35",
4
4
  "private": false,
5
5
  "main": "scripts/glama-start.mjs",
6
6
  "bin": {
package/src/google.ts CHANGED
@@ -785,10 +785,9 @@ export async function querySearchAnalytics(
785
785
 
786
786
  /**
787
787
  * Fetches Search Analytics rows in 25,000-row pages for higher-level tools.
788
- * Google's documented pagination contract uses an explicit empty response as
789
- * the terminal page, so a short non-empty page is not treated as exhaustion.
790
- * A full final page conservatively reports localLimitReached because the API
791
- * can have more rows even when Search Console's own internal limits apply.
788
+ * Google's documented pagination contract treats a response shorter than the
789
+ * requested rowLimit as exhausted. An exactly full final page still requires
790
+ * a following empty-page probe unless the local safety ceiling is reached.
792
791
  */
793
792
  export async function querySearchAnalyticsPaginated(
794
793
  accessToken: string,
@@ -820,7 +819,7 @@ export async function querySearchAnalyticsPaginated(
820
819
 
821
820
  const acceptedRows = page.rows.slice(0, requestedRows);
822
821
  rows.push(...acceptedRows);
823
- if (page.rows.length === 0) {
822
+ if (page.rows.length < requestedRows) {
824
823
  return { rows, pagesFetched, localLimitReached: false };
825
824
  }
826
825
  if (rows.length >= maximumRows || page.rows.length > requestedRows) {
package/src/index.ts CHANGED
@@ -1408,11 +1408,10 @@ class GscMcpRuntime {
1408
1408
  if (rateLimitError) return rateLimitError;
1409
1409
  const accessToken = await this.getAccessToken(googleId);
1410
1410
  const response = await querySearchAnalytics(accessToken, site_url, query);
1411
- // Search Analytics pagination is complete only after Google returns an
1412
- // explicit empty page. A short non-empty page can still be followed by
1413
- // later rows, so preserve a continuation for dimensioned queries.
1411
+ // Google documents a short page as terminal. An exactly full page can
1412
+ // still have a following page, so expose continuation only then.
1414
1413
  const sourceMayHaveMore =
1415
- dimensions.length > 0 && response.rows.length > 0;
1414
+ dimensions.length > 0 && response.rows.length === sourceRowLimit;
1416
1415
  const bounded = windowSourceRows(
1417
1416
  response.rows,
1418
1417
  start_row,
@@ -1463,10 +1462,10 @@ class GscMcpRuntime {
1463
1462
  'insights.page_queries',
1464
1463
  {
1465
1464
  title: 'Find queries for a page',
1466
- description: 'For one exact page URL, return the Search Console queries that produced impressions for it over a date range. This wraps an exact page dimension filter so agents do not need to construct analytics.query filter groups manually. Exact page matching is case-sensitive in Search Console. Use row_limit and start_row to page through bounded results; Search Console can still omit anonymized queries. Google Discover and Google News are intentionally unavailable because those reports do not expose query data.',
1465
+ description: 'For one exact page URL within the supplied Search Console property, return the queries that produced impressions for it over a date range. This wraps an exact page dimension filter so agents do not need to construct analytics.query filter groups manually. Exact page matching is case-sensitive in Search Console. Use row_limit and start_row to page through bounded results; Search Console can still omit anonymized queries. Google Discover and Google News are intentionally unavailable because those reports do not expose query data.',
1467
1466
  inputSchema: {
1468
1467
  site_url: SEARCH_CONSOLE_PROPERTY_SCHEMA,
1469
- page_url: z.string().url().describe('Exact fully-qualified page URL to filter on, e.g. https://example.com/guides/seo/. Search Console exact page filters are case-sensitive.'),
1468
+ page_url: z.string().url().describe('Exact fully-qualified page URL to filter on, e.g. https://example.com/guides/seo/. The URL must belong to site_url; Search Console exact page filters are case-sensitive.'),
1470
1469
  start_date: SEARCH_CONSOLE_DATE_SCHEMA.describe(
1471
1470
  searchConsoleDateDescription('Start date (inclusive) in YYYY-MM-DD format.'),
1472
1471
  ),
@@ -1498,6 +1497,7 @@ class GscMcpRuntime {
1498
1497
  },
1499
1498
  async ({ site_url, page_url, start_date, end_date, search_type, row_limit = 100, start_row = 0 }) => {
1500
1499
  assertDateRange(start_date, end_date);
1500
+ assertUrlWithinSearchConsoleProperty(page_url, site_url, 'page_url');
1501
1501
  const googleId = this.requireGoogleId();
1502
1502
  const rateLimitError = await this.rateLimitError(googleId, 'insights.page_queries');
1503
1503
  if (rateLimitError) return rateLimitError;
@@ -2124,7 +2124,7 @@ class GscMcpRuntime {
2124
2124
  pages,
2125
2125
  start_row,
2126
2126
  row_limit,
2127
- response.rows.length > 0,
2127
+ response.rows.length === sourceRowLimit,
2128
2128
  start_row + sourceRowLimit,
2129
2129
  );
2130
2130