gsc_striking_distance
Identify queries ranking in positions 4–20 (striking distance) to find low-hanging fruit for quick ranking improvements. Results sorted by impressions descending.
Instructions
Find queries ranking in positions 4–20 (striking distance / low-hanging fruit). These are the best candidates for quick ranking improvements. Results sorted by impressions descending.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| site_url | No | Site URL in GSC format, e.g. 'sc-domain:example.com'. Uses config default if omitted. | |
| start_date | Yes | Start date in YYYY-MM-DD format. | |
| end_date | Yes | End date in YYYY-MM-DD format. | |
| min_position | No | Minimum average position to include. Default: 4. | |
| max_position | No | Maximum average position to include. Default: 20. | |
| min_impressions | No | Minimum impressions to include. Default: 10. | |
| row_limit | No | Max rows to return. Default: 50. |
Implementation Reference
- src/tools/gsc/striking-distance.ts:23-81 (handler)The handler function that executes the gsc_striking_distance tool logic: queries GSC Search Analytics, filters for positions 4-20 (striking distance), sorts by impressions descending, and returns results as a tab-separated table.
handler: async (args, config) => { const auth = getOAuth2Client(); const sc = google.searchconsole({ version: "v1", auth }); const siteUrl = args.site_url ?? config.gsc?.default_site; if (!siteUrl) { throw new Error( "site_url is required. Pass it as an argument or set gsc.default_site in ~/.seo-mcp/config.json" ); } const minPos = args.min_position ?? 4; const maxPos = args.max_position ?? 20; const minImpressions = args.min_impressions ?? 10; const rowLimit = args.row_limit ?? 50; const res = await sc.searchanalytics.query({ siteUrl, requestBody: { startDate: args.start_date, endDate: args.end_date, dimensions: ["query"], rowLimit: 5000, }, }); const rows = res.data.rows ?? []; const filtered = rows .filter((r) => { const pos = r.position ?? 0; const imp = r.impressions ?? 0; return pos >= minPos && pos <= maxPos && imp >= minImpressions; }) .sort((a, b) => (b.impressions ?? 0) - (a.impressions ?? 0)) .slice(0, rowLimit); if (filtered.length === 0) { return { content: [ { type: "text", text: `No queries found in positions ${minPos}–${maxPos} with ≥${minImpressions} impressions.`, }, ], }; } const summary = `Found ${filtered.length} queries in striking distance (positions ${minPos}–${maxPos}).\n\n`; const header = "query\tposition\timpressions\tclicks\tctr"; const lines = filtered.map((r) => { const query = r.keys?.[0] ?? ""; const pos = r.position?.toFixed(1) ?? "—"; const ctr = ((r.ctr ?? 0) * 100).toFixed(2) + "%"; return `${query}\t${pos}\t${r.impressions ?? 0}\t${r.clicks ?? 0}\t${ctr}`; }); return { content: [{ type: "text", text: summary + [header, ...lines].join("\n") }] }; }, }; - Zod schema defining input parameters: site_url (optional), start_date, end_date, min_position (default 4), max_position (default 20), min_impressions (default 10), row_limit (default 50).
const schema = z.object({ site_url: z.string().optional().describe( "Site URL in GSC format, e.g. 'sc-domain:example.com'. Uses config default if omitted." ), start_date: z.string().describe("Start date in YYYY-MM-DD format."), end_date: z.string().describe("End date in YYYY-MM-DD format."), min_position: z.number().optional().describe("Minimum average position to include. Default: 4."), max_position: z.number().optional().describe("Maximum average position to include. Default: 20."), min_impressions: z.number().optional().describe("Minimum impressions to include. Default: 10."), row_limit: z.number().optional().describe("Max rows to return. Default: 50."), }); - src/tools/gsc/index.ts:2-16 (registration)Import and registration of gscStrikingDistance in the gscTools array, which is the list of all GSC tools.
import { gscStrikingDistance } from "./striking-distance.js"; import { gscTrafficDrop } from "./traffic-drop.js"; import { gscUrlInspection } from "./url-inspection.js"; import { gscSitemapList } from "./sitemap-list.js"; import { gscBrandNonbrand } from "./brand-nonbrand.js"; import type { ToolDefinition } from "../../types/tool.js"; export const gscTools: ToolDefinition[] = [ gscSearchPerformance as unknown as ToolDefinition, gscStrikingDistance as unknown as ToolDefinition, gscTrafficDrop as unknown as ToolDefinition, gscUrlInspection as unknown as ToolDefinition, gscSitemapList as unknown as ToolDefinition, gscBrandNonbrand as unknown as ToolDefinition, ]; - src/tools/gsc/index.ts:11-16 (registration)Explicit registration of gscStrikingDistance as a ToolDefinition in the gscTools array.
gscStrikingDistance as unknown as ToolDefinition, gscTrafficDrop as unknown as ToolDefinition, gscUrlInspection as unknown as ToolDefinition, gscSitemapList as unknown as ToolDefinition, gscBrandNonbrand as unknown as ToolDefinition, ]; - src/types/tool.ts:12-17 (helper)ToolDefinition type that defines the structure of a tool including name, description, schema, and handler.
export interface ToolDefinition<T extends AnyZodObject = AnyZodObject> { name: string; description: string; schema: T; handler: (args: z.infer<T>, config: Config) => Promise<ToolResult>; }