@gscdump/cli 1.4.0 → 1.4.2

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.
Files changed (54) hide show
  1. package/dist/{_chunks/analysis-local.mjs → analysis-local.mjs} +4 -3
  2. package/dist/{_chunks/auth.mjs → auth.mjs} +2 -2
  3. package/dist/cli.d.mts +1 -19
  4. package/dist/cli.mjs +25 -25
  5. package/dist/{_chunks → commands}/analyze.mjs +3 -3
  6. package/dist/{_chunks/auth2.mjs → commands/auth.mjs} +4 -4
  7. package/dist/commands/compact.mjs +128 -0
  8. package/dist/{_chunks/config2.mjs → commands/config.mjs} +4 -4
  9. package/dist/{_chunks → commands}/doctor.mjs +8 -7
  10. package/dist/{_chunks → commands}/dump.mjs +5 -4
  11. package/dist/{_chunks → commands}/entities.mjs +2 -2
  12. package/dist/commands/export.mjs +76 -0
  13. package/dist/commands/gc.mjs +74 -0
  14. package/dist/{_chunks → commands}/indexing.mjs +3 -3
  15. package/dist/{_chunks → commands}/init.mjs +5 -5
  16. package/dist/{_chunks → commands}/inspect.mjs +3 -3
  17. package/dist/commands/mcp.mjs +57 -0
  18. package/dist/{_chunks → commands}/profile.mjs +7 -17
  19. package/dist/{_chunks → commands}/query.mjs +5 -4
  20. package/dist/{_chunks → commands}/report.mjs +2 -2
  21. package/dist/commands/rollups.mjs +134 -0
  22. package/dist/{_chunks → commands}/sitemaps.mjs +3 -3
  23. package/dist/{_chunks → commands}/sites.mjs +3 -3
  24. package/dist/commands/stats.mjs +118 -0
  25. package/dist/commands/store-purge.mjs +93 -0
  26. package/dist/commands/store.mjs +40 -0
  27. package/dist/{_chunks → commands}/sync.mjs +3 -2
  28. package/dist/config.mjs +43 -0
  29. package/dist/{_chunks/context.mjs → context.mjs} +3 -9
  30. package/dist/{_chunks/env-file.mjs → env-file.mjs} +3 -3
  31. package/dist/{_chunks/environment.mjs → environment.mjs} +1 -1
  32. package/dist/index.d.mts +2 -0
  33. package/dist/index.mjs +2 -0
  34. package/dist/local-store.mjs +7 -0
  35. package/dist/mcp/errors.mjs +85 -0
  36. package/dist/mcp/handlers/analytics.mjs +1 -0
  37. package/dist/mcp/handlers/diagnostics.mjs +134 -0
  38. package/dist/mcp/handlers/index.mjs +7 -0
  39. package/dist/mcp/handlers/indexing.mjs +27 -0
  40. package/dist/mcp/handlers/query.mjs +5 -0
  41. package/dist/mcp/handlers/reports.mjs +72 -0
  42. package/dist/mcp/handlers/sites.mjs +8 -0
  43. package/dist/mcp/server/index.mjs +417 -0
  44. package/dist/mcp/types.mjs +86 -0
  45. package/dist/package.mjs +2 -0
  46. package/dist/runtime.d.mts +20 -0
  47. package/dist/runtime.mjs +38 -0
  48. package/dist/{_chunks/utils.mjs → utils.mjs} +4 -4
  49. package/package.json +8 -8
  50. package/dist/_chunks/config.mjs +0 -93
  51. package/dist/_chunks/mcp.mjs +0 -867
  52. package/dist/_chunks/store.mjs +0 -628
  53. /package/dist/{_chunks/error-handler.mjs → error-handler.mjs} +0 -0
  54. /package/dist/{_chunks/native-duckdb.mjs → native-duckdb.mjs} +0 -0
@@ -0,0 +1,134 @@
1
+ import { hasGscWriteScope, hasIndexingScope } from "gscdump";
2
+ import { ofetch } from "ofetch";
3
+ const REQUIRED_SCOPES = [
4
+ "https://www.googleapis.com/auth/webmasters",
5
+ "https://www.googleapis.com/auth/indexing",
6
+ "https://www.googleapis.com/auth/siteverification"
7
+ ];
8
+ const FETCH_TIMEOUT_MS = 5e3;
9
+ const TIME_SKEW_WARN_MS = 5 * 6e4;
10
+ function hasGoogleScope(scopes, scope) {
11
+ const suffix = scope.replace("https://www.googleapis.com/auth/", "");
12
+ return scopes.includes(scope) || scopes.includes(suffix);
13
+ }
14
+ function missingRequiredScopes(scopes) {
15
+ return REQUIRED_SCOPES.filter((scope) => {
16
+ if (scope.endsWith("/webmasters")) return !hasGscWriteScope(scopes);
17
+ if (scope.endsWith("/indexing")) return !hasIndexingScope(scopes);
18
+ return !hasGoogleScope(scopes, scope);
19
+ });
20
+ }
21
+ async function diagnostics(_input, ctx) {
22
+ const checks = [];
23
+ const token = await resolveAccessToken(ctx.auth);
24
+ if (!token) {
25
+ checks.push({
26
+ name: "auth",
27
+ status: "fail",
28
+ detail: "no usable access token (refresh failed or no credentials)"
29
+ });
30
+ return {
31
+ ok: false,
32
+ checks
33
+ };
34
+ }
35
+ const [tokenInfo, timeCheck, gscReachable, indexingReachable, sites] = await Promise.all([
36
+ ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: token } }).catch((e) => ({ error: e.message })),
37
+ probeTimeSkew(),
38
+ probeReachable("https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
39
+ probeReachable("https://indexing.googleapis.com/$discovery/rest?version=v3"),
40
+ ctx.client.sites().catch((e) => e)
41
+ ]);
42
+ if ("error" in tokenInfo) checks.push({
43
+ name: "auth",
44
+ status: "fail",
45
+ detail: `tokeninfo failed: ${tokenInfo.error}`
46
+ });
47
+ else {
48
+ checks.push({
49
+ name: "auth",
50
+ status: "pass",
51
+ detail: tokenInfo.email ?? "token valid"
52
+ });
53
+ const scopes = tokenInfo.scope ? tokenInfo.scope.split(/\s+/) : [];
54
+ const missing = missingRequiredScopes(scopes);
55
+ checks.push(missing.length > 0 ? {
56
+ name: "auth.scopes",
57
+ status: "warn",
58
+ detail: `missing: ${missing.join(", ")}`
59
+ } : {
60
+ name: "auth.scopes",
61
+ status: "pass",
62
+ detail: `${scopes.length} granted`
63
+ });
64
+ }
65
+ checks.push(timeCheck);
66
+ checks.push({
67
+ name: "gsc.api",
68
+ status: gscReachable ? "pass" : "warn",
69
+ detail: gscReachable ? "reachable" : "searchconsole.googleapis.com unreachable"
70
+ });
71
+ checks.push({
72
+ name: "indexing.api",
73
+ status: indexingReachable ? "pass" : "warn",
74
+ detail: indexingReachable ? "reachable" : "indexing.googleapis.com unreachable"
75
+ });
76
+ if (sites instanceof Error) checks.push({
77
+ name: "gsc.sites",
78
+ status: "fail",
79
+ detail: `sites() failed: ${sites.message}`
80
+ });
81
+ else {
82
+ const verified = sites.filter((s) => s.permissionLevel !== "siteUnverifiedUser").length;
83
+ checks.push({
84
+ name: "gsc.sites",
85
+ status: "pass",
86
+ detail: `${sites.length} site(s) accessible (${verified} verified)`
87
+ });
88
+ }
89
+ return {
90
+ ok: !checks.some((c) => c.status === "fail"),
91
+ checks
92
+ };
93
+ }
94
+ async function resolveAccessToken(auth) {
95
+ if (typeof auth === "string") return auth;
96
+ if (auth && typeof auth === "object" && "getAccessToken" in auth) return (await auth.getAccessToken().catch(() => null))?.token ?? null;
97
+ return null;
98
+ }
99
+ async function probeTimeSkew() {
100
+ const dateHeader = await ofetch.raw("https://oauth2.googleapis.com/tokeninfo", {
101
+ method: "GET",
102
+ timeout: FETCH_TIMEOUT_MS
103
+ }).then((r) => r.headers.get("date")).catch((e) => e?.response?.headers?.get("date") ?? null);
104
+ if (!dateHeader) return {
105
+ name: "time",
106
+ status: "warn",
107
+ detail: "no Date header"
108
+ };
109
+ const remoteMs = Date.parse(dateHeader);
110
+ if (!Number.isFinite(remoteMs)) return {
111
+ name: "time",
112
+ status: "warn",
113
+ detail: `unparseable Date header: ${dateHeader}`
114
+ };
115
+ const skewMs = Date.now() - remoteMs;
116
+ const human = `${skewMs >= 0 ? "+" : ""}${(skewMs / 1e3).toFixed(1)}s`;
117
+ if (Math.abs(skewMs) > TIME_SKEW_WARN_MS) return {
118
+ name: "time",
119
+ status: "warn",
120
+ detail: `local clock ${human} off Google`
121
+ };
122
+ return {
123
+ name: "time",
124
+ status: "pass",
125
+ detail: `in sync (${human})`
126
+ };
127
+ }
128
+ async function probeReachable(url) {
129
+ return ofetch.raw(url, {
130
+ method: "GET",
131
+ timeout: FETCH_TIMEOUT_MS
132
+ }).then(() => true).catch(() => false);
133
+ }
134
+ export { diagnostics };
@@ -0,0 +1,7 @@
1
+ import "./analytics.mjs";
2
+ import { diagnostics } from "./diagnostics.mjs";
3
+ import { batchInspectUrls, batchRequestIndexing, getIndexingStatus, requestIndexing } from "./indexing.mjs";
4
+ import "./query.mjs";
5
+ import { listReports, runReportHandler, runReportHandlerResult } from "./reports.mjs";
6
+ import { getSitemap, listSitesWithSitemaps } from "./sites.mjs";
7
+ export { batchInspectUrls, batchRequestIndexing, diagnostics, getIndexingStatus, getSitemap, listReports, listSitesWithSitemaps, requestIndexing, runReportHandler, runReportHandlerResult };
@@ -0,0 +1,27 @@
1
+ import { batchInspectUrls, batchRequestIndexing, getIndexingMetadata, requestIndexing } from "gscdump";
2
+ async function requestIndexing$1(input, ctx) {
3
+ return requestIndexing(ctx.client, input.url, { type: input.type || "URL_UPDATED" });
4
+ }
5
+ async function getIndexingStatus(input, ctx) {
6
+ return getIndexingMetadata(ctx.client, input.url);
7
+ }
8
+ async function batchRequestIndexing$1(input, ctx) {
9
+ const results = await batchRequestIndexing(ctx.client, input.urls, {
10
+ type: input.type || "URL_UPDATED",
11
+ delayMs: input.delayMs || 100
12
+ });
13
+ return {
14
+ results,
15
+ success: results.length,
16
+ failed: 0
17
+ };
18
+ }
19
+ async function batchInspectUrls$1(input, ctx) {
20
+ const results = await batchInspectUrls(ctx.client, input.siteUrl, input.urls, { delayMs: input.delayMs || 200 });
21
+ return {
22
+ results,
23
+ indexed: results.filter((r) => r.isIndexed).length,
24
+ notIndexed: results.filter((r) => !r.isIndexed).length
25
+ };
26
+ }
27
+ export { batchInspectUrls$1 as batchInspectUrls, batchRequestIndexing$1 as batchRequestIndexing, getIndexingStatus, requestIndexing$1 as requestIndexing };
@@ -0,0 +1,5 @@
1
+ import "./analytics.mjs";
2
+ import "../errors.mjs";
3
+ import "gscdump/query";
4
+ import "gscdump/result";
5
+ export {};
@@ -0,0 +1,72 @@
1
+ import { enrichToolError, mcpHandlerErrorToException, mcpHandlerErrors } from "../errors.mjs";
2
+ import { defaultAnalyzerRegistry } from "@gscdump/analysis/registry";
3
+ import { createGscApiQuerySource } from "@gscdump/engine-gsc-api";
4
+ import { err, ok, unwrapResult } from "gscdump/result";
5
+ import { defaultReportRegistry, runReport } from "@gscdump/analysis/report";
6
+ import { resolveWindow } from "@gscdump/engine/period";
7
+ const PERIOD_ALIASES = {
8
+ "7d": "last-7d",
9
+ "28d": "last-28d",
10
+ "30d": "last-30d",
11
+ "90d": "last-90d",
12
+ "180d": "last-180d",
13
+ "365d": "last-365d",
14
+ "mtd": "mtd",
15
+ "ytd": "ytd",
16
+ "custom": "custom"
17
+ };
18
+ const COMPARISON_ALIASES = {
19
+ "none": "none",
20
+ "prev": "prev-period",
21
+ "prev-period": "prev-period",
22
+ "prior": "prev-period",
23
+ "yoy": "yoy"
24
+ };
25
+ function listReports() {
26
+ return defaultReportRegistry.listReports().map((r) => ({
27
+ id: r.id,
28
+ description: r.description,
29
+ defaultPeriod: r.defaultPeriod,
30
+ defaultComparison: r.defaultComparison,
31
+ argsSpec: r.argsSpec
32
+ }));
33
+ }
34
+ async function runReportHandlerResult(input, ctx) {
35
+ const report = defaultReportRegistry.getReport(input.id);
36
+ if (!report) return err(mcpHandlerErrors.unknownReport(input.id, defaultReportRegistry.listReportIds()));
37
+ const preset = input.period ? PERIOD_ALIASES[input.period.toLowerCase()] ?? null : report.defaultPeriod;
38
+ if (!preset) return err(mcpHandlerErrors.unknownPeriod(input.period ?? ""));
39
+ const comparison = input.comparison ? COMPARISON_ALIASES[input.comparison.toLowerCase()] ?? null : report.defaultComparison;
40
+ if (!comparison) return err(mcpHandlerErrors.unknownComparison(input.comparison ?? ""));
41
+ const window = resolveWindow({
42
+ preset,
43
+ comparison,
44
+ start: input.start,
45
+ end: input.end
46
+ });
47
+ if (input.prevStart && input.prevEnd) window.comparison = {
48
+ start: input.prevStart,
49
+ end: input.prevEnd
50
+ };
51
+ const params = {};
52
+ if (input.maxFindings != null) params.maxFindings = input.maxFindings;
53
+ return ok(await runReport(report, {
54
+ source: createGscApiQuerySource({
55
+ client: ctx.client,
56
+ siteUrl: input.siteUrl
57
+ }),
58
+ analyzers: defaultAnalyzerRegistry,
59
+ ctx: {
60
+ site: input.siteUrl,
61
+ window,
62
+ params,
63
+ registryVersion: defaultReportRegistry.version
64
+ }
65
+ }));
66
+ }
67
+ async function runReportHandler(input, ctx) {
68
+ return unwrapResult(await runReportHandlerResult(input, ctx).catch((thrown) => {
69
+ throw enrichToolError(thrown) ?? thrown;
70
+ }), mcpHandlerErrorToException);
71
+ }
72
+ export { listReports, runReportHandler, runReportHandlerResult };
@@ -0,0 +1,8 @@
1
+ import { fetchSitemap, fetchSitesWithSitemaps } from "gscdump";
2
+ async function listSitesWithSitemaps(_input, ctx) {
3
+ return fetchSitesWithSitemaps(ctx.client);
4
+ }
5
+ async function getSitemap(input, ctx) {
6
+ return fetchSitemap(ctx.client, input.siteUrl, input.feedpath);
7
+ }
8
+ export { getSitemap, listSitesWithSitemaps };
@@ -0,0 +1,417 @@
1
+ import { diagnostics } from "../handlers/diagnostics.mjs";
2
+ import { batchInspectUrls as batchInspectUrls$1, batchRequestIndexing as batchRequestIndexing$1, getIndexingStatus, requestIndexing as requestIndexing$1 } from "../handlers/indexing.mjs";
3
+ import { listReports, runReportHandler } from "../handlers/reports.mjs";
4
+ import { getSitemap, listSitesWithSitemaps } from "../handlers/sites.mjs";
5
+ import "../handlers/index.mjs";
6
+ import { batchInspectUrlsInput, batchRequestIndexingInput, getIndexingStatusInput, inspectUrlInput, listReportsInput, listSitemapsInput, listSitesInput, requestIndexingInput, runReportInput, sitemapInput } from "../types.mjs";
7
+ import { addSite, deleteSite, discoverSitemap, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, listVerifiedSites, runSequentialBatch, unverifySite, verifySite } from "gscdump";
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { z } from "zod";
10
+ async function runMcpSearchAnalyticsQuery(client, args) {
11
+ const totalLimit = Math.max(0, args.rowLimit ?? 25e3);
12
+ const pageSize = Math.min(totalLimit, 25e3);
13
+ const allRows = [];
14
+ let startRow = 0;
15
+ while (allRows.length < totalLimit) {
16
+ const remaining = totalLimit - allRows.length;
17
+ const currentLimit = Math.min(pageSize, remaining);
18
+ if (currentLimit <= 0) break;
19
+ const rows = ((await client.searchAnalytics.query(args.siteUrl, {
20
+ startDate: args.startDate,
21
+ endDate: args.endDate,
22
+ dimensions: args.dimensions,
23
+ rowLimit: currentLimit,
24
+ startRow,
25
+ ...args.type ? { type: args.type } : {},
26
+ ...args.dataState ? { dataState: args.dataState } : {},
27
+ ...args.aggregationType ? { aggregationType: args.aggregationType } : {},
28
+ ...args.dimensionFilterGroups ? { dimensionFilterGroups: args.dimensionFilterGroups.map((g) => ({
29
+ groupType: g.groupType ?? "and",
30
+ filters: g.filters
31
+ })) } : {}
32
+ })).rows || []).map((row) => {
33
+ const result = {
34
+ clicks: row.clicks ?? 0,
35
+ impressions: row.impressions ?? 0,
36
+ ctr: row.ctr ?? 0,
37
+ position: row.position ?? 0
38
+ };
39
+ args.dimensions.forEach((dim, i) => {
40
+ result[dim] = row.keys?.[i];
41
+ });
42
+ return result;
43
+ });
44
+ if (rows.length === 0) break;
45
+ allRows.push(...rows);
46
+ startRow += rows.length;
47
+ }
48
+ return {
49
+ siteUrl: args.siteUrl,
50
+ rowCount: allRows.length,
51
+ rows: allRows
52
+ };
53
+ }
54
+ function createGscMcpServer(options) {
55
+ const { name = "gscdump", version = "1.0.0", getAuth } = options;
56
+ const server = new McpServer({
57
+ name,
58
+ version
59
+ });
60
+ const auth = async () => Promise.resolve(getAuth());
61
+ const getContext = async () => {
62
+ const a = await auth();
63
+ return {
64
+ auth: a,
65
+ client: googleSearchConsole(a)
66
+ };
67
+ };
68
+ const getClient = async () => googleSearchConsole(await auth());
69
+ server.registerTool("list-sites", {
70
+ description: "List all Google Search Console sites visible to the authenticated user.",
71
+ inputSchema: listSitesInput.shape
72
+ }, async () => {
73
+ const sites = (await (await getClient()).sites()).filter((s) => s.siteUrl && s.permissionLevel !== "siteUnverifiedUser").map((s) => ({
74
+ siteUrl: s.siteUrl,
75
+ permissionLevel: s.permissionLevel || "unknown"
76
+ }));
77
+ return { content: [{
78
+ type: "text",
79
+ text: JSON.stringify(sites, null, 2)
80
+ }] };
81
+ });
82
+ server.registerTool("list-sites-with-sitemaps", {
83
+ description: "List all GSC sites with their sitemaps",
84
+ inputSchema: listSitesInput.shape
85
+ }, async (args) => {
86
+ const result = await listSitesWithSitemaps(args, await getContext());
87
+ return { content: [{
88
+ type: "text",
89
+ text: JSON.stringify(result, null, 2)
90
+ }] };
91
+ });
92
+ server.registerTool("list-sitemaps", {
93
+ description: "List sitemaps for a specific site",
94
+ inputSchema: listSitemapsInput.shape
95
+ }, async (args) => {
96
+ const sitemaps = await (await getClient()).sitemaps.list(args.siteUrl);
97
+ return { content: [{
98
+ type: "text",
99
+ text: JSON.stringify(sitemaps, null, 2)
100
+ }] };
101
+ });
102
+ server.registerTool("get-sitemap", {
103
+ description: "Get details for a specific sitemap",
104
+ inputSchema: sitemapInput.shape
105
+ }, async (args) => {
106
+ const result = await getSitemap(args, await getContext());
107
+ return { content: [{
108
+ type: "text",
109
+ text: JSON.stringify(result, null, 2)
110
+ }] };
111
+ });
112
+ server.registerTool("submit-sitemap", {
113
+ description: "Submit a sitemap to Google Search Console",
114
+ inputSchema: sitemapInput.shape
115
+ }, async (args) => {
116
+ await (await getClient()).sitemaps.submit(args.siteUrl, args.feedpath);
117
+ return { content: [{
118
+ type: "text",
119
+ text: JSON.stringify({ success: true }, null, 2)
120
+ }] };
121
+ });
122
+ server.registerTool("delete-sitemap", {
123
+ description: "Delete a sitemap from Google Search Console",
124
+ inputSchema: sitemapInput.shape
125
+ }, async (args) => {
126
+ await (await getClient()).sitemaps.delete(args.siteUrl, args.feedpath);
127
+ return { content: [{
128
+ type: "text",
129
+ text: JSON.stringify({ success: true }, null, 2)
130
+ }] };
131
+ });
132
+ server.registerTool("list-reports", {
133
+ description: "List available reports (intent-keyed analyzer compositions). Returns id, description, default period/comparison, and per-report argsSpec.",
134
+ inputSchema: listReportsInput.shape
135
+ }, async () => {
136
+ const result = listReports();
137
+ return { content: [{
138
+ type: "text",
139
+ text: JSON.stringify(result, null, 2)
140
+ }] };
141
+ });
142
+ server.registerTool("run-report", {
143
+ description: "Run a report against the GSC API. Returns a structured ReportResult with bounded findings per section. See list-reports for ids.",
144
+ inputSchema: runReportInput.shape
145
+ }, async (args) => {
146
+ const result = await runReportHandler(args, await getContext());
147
+ return { content: [{
148
+ type: "text",
149
+ text: JSON.stringify(result, null, 2)
150
+ }] };
151
+ });
152
+ const filterOperatorSchema = z.enum([
153
+ "equals",
154
+ "notEquals",
155
+ "contains",
156
+ "notContains",
157
+ "includingRegex",
158
+ "excludingRegex"
159
+ ]);
160
+ const dimensionFilterSchema = z.object({
161
+ dimension: z.enum([
162
+ "date",
163
+ "query",
164
+ "page",
165
+ "country",
166
+ "device",
167
+ "searchAppearance"
168
+ ]),
169
+ operator: filterOperatorSchema,
170
+ expression: z.string()
171
+ });
172
+ const filterGroupSchema = z.object({
173
+ groupType: z.enum(["and"]).optional().describe("Always \"and\"; multiple groups are OR-ed together"),
174
+ filters: z.array(dimensionFilterSchema)
175
+ });
176
+ server.registerTool("query", {
177
+ description: "Run a custom search analytics query. Supports dimension filters (regex/contains/equals) via dimensionFilterGroups; multiple groups are OR-ed.",
178
+ inputSchema: z.object({
179
+ siteUrl: z.string().describe("GSC property URL (e.g., sc-domain:example.com)"),
180
+ startDate: z.string().describe("Start date (YYYY-MM-DD)"),
181
+ endDate: z.string().describe("End date (YYYY-MM-DD)"),
182
+ dimensions: z.array(z.enum([
183
+ "date",
184
+ "query",
185
+ "page",
186
+ "country",
187
+ "device",
188
+ "searchAppearance"
189
+ ])).describe("Dimensions to group by"),
190
+ rowLimit: z.number().optional().describe("Max rows (default 25000)"),
191
+ type: z.enum([
192
+ "web",
193
+ "image",
194
+ "video",
195
+ "news",
196
+ "discover",
197
+ "googleNews"
198
+ ]).optional().describe("Search type"),
199
+ dataState: z.enum(["final", "all"]).optional().describe("Data state: final (settled) or all (includes fresh)"),
200
+ aggregationType: z.enum(["byPage", "byProperty"]).optional().describe("Aggregation type"),
201
+ dimensionFilterGroups: z.array(filterGroupSchema).optional().describe("Filter groups (each \"and\"-ed internally; multiple groups are OR-ed)")
202
+ }).shape
203
+ }, async ({ siteUrl, startDate, endDate, dimensions, rowLimit, type, dataState, aggregationType, dimensionFilterGroups }) => {
204
+ const result = await runMcpSearchAnalyticsQuery(await getClient(), {
205
+ siteUrl,
206
+ startDate,
207
+ endDate,
208
+ dimensions,
209
+ rowLimit,
210
+ type,
211
+ dataState,
212
+ aggregationType,
213
+ dimensionFilterGroups
214
+ });
215
+ return { content: [{
216
+ type: "text",
217
+ text: JSON.stringify(result, null, 2)
218
+ }] };
219
+ });
220
+ server.registerTool("inspect-url", {
221
+ description: "Inspect a URL to check its indexing status in Google Search Console",
222
+ inputSchema: inspectUrlInput.shape
223
+ }, async (args) => {
224
+ const result = await (await getClient()).inspect(args.siteUrl, args.inspectionUrl);
225
+ return { content: [{
226
+ type: "text",
227
+ text: JSON.stringify(result, null, 2)
228
+ }] };
229
+ });
230
+ server.registerTool("request-indexing", {
231
+ description: "Request Google to index or remove a URL via the Indexing API",
232
+ inputSchema: requestIndexingInput.shape
233
+ }, async (args) => {
234
+ const result = await requestIndexing$1(args, await getContext());
235
+ return { content: [{
236
+ type: "text",
237
+ text: JSON.stringify(result, null, 2)
238
+ }] };
239
+ });
240
+ server.registerTool("get-indexing-status", {
241
+ description: "Get indexing status metadata for a URL",
242
+ inputSchema: getIndexingStatusInput.shape
243
+ }, async (args) => {
244
+ const result = await getIndexingStatus(args, await getContext());
245
+ return { content: [{
246
+ type: "text",
247
+ text: JSON.stringify(result, null, 2)
248
+ }] };
249
+ });
250
+ server.registerTool("batch-request-indexing", {
251
+ description: "Batch request indexing for multiple URLs with rate limiting",
252
+ inputSchema: batchRequestIndexingInput.shape
253
+ }, async (args) => {
254
+ const result = await batchRequestIndexing$1(args, await getContext());
255
+ return { content: [{
256
+ type: "text",
257
+ text: JSON.stringify(result, null, 2)
258
+ }] };
259
+ });
260
+ server.registerTool("batch-inspect-urls", {
261
+ description: "Batch inspect multiple URLs to check their indexing status",
262
+ inputSchema: batchInspectUrlsInput.shape
263
+ }, async (args) => {
264
+ const result = await batchInspectUrls$1(args, await getContext());
265
+ return { content: [{
266
+ type: "text",
267
+ text: JSON.stringify(result, null, 2)
268
+ }] };
269
+ });
270
+ server.registerTool("diagnostics", {
271
+ description: "Run health checks on the active GSC connection: auth/scopes, time skew, API reachability, sites count.",
272
+ inputSchema: listSitesInput.shape
273
+ }, async (args) => {
274
+ const result = await diagnostics(args, await getContext());
275
+ return { content: [{
276
+ type: "text",
277
+ text: JSON.stringify(result, null, 2)
278
+ }] };
279
+ });
280
+ server.registerTool("add-site", {
281
+ description: "Register a property in Search Console (unverified). Verify ownership separately.",
282
+ inputSchema: z.object({ siteUrl: z.string().describe("Property URL (https://example.com/ or sc-domain:example.com)") }).shape
283
+ }, async ({ siteUrl }) => {
284
+ await addSite(await getClient(), siteUrl);
285
+ return { content: [{
286
+ type: "text",
287
+ text: JSON.stringify({
288
+ siteUrl,
289
+ status: "added",
290
+ verified: false
291
+ }, null, 2)
292
+ }] };
293
+ });
294
+ server.registerTool("delete-site", {
295
+ description: "Remove a property from Search Console.",
296
+ inputSchema: z.object({ siteUrl: z.string().describe("Property URL") }).shape
297
+ }, async ({ siteUrl }) => {
298
+ await deleteSite(await getClient(), siteUrl);
299
+ return { content: [{
300
+ type: "text",
301
+ text: JSON.stringify({
302
+ siteUrl,
303
+ status: "deleted"
304
+ }, null, 2)
305
+ }] };
306
+ });
307
+ const verificationMethodSchema = z.enum([
308
+ "META",
309
+ "FILE",
310
+ "DNS_TXT",
311
+ "DNS_CNAME",
312
+ "ANALYTICS",
313
+ "TAG_MANAGER"
314
+ ]);
315
+ server.registerTool("get-verification-token", {
316
+ description: "Get a verification token to place on the site or in DNS.",
317
+ inputSchema: z.object({
318
+ siteUrl: z.string(),
319
+ method: verificationMethodSchema
320
+ }).shape
321
+ }, async ({ siteUrl, method }) => {
322
+ const result = await getVerificationToken(await getClient(), siteUrl, method);
323
+ return { content: [{
324
+ type: "text",
325
+ text: JSON.stringify({
326
+ siteUrl,
327
+ method,
328
+ token: result.token,
329
+ site: result.site
330
+ }, null, 2)
331
+ }] };
332
+ });
333
+ server.registerTool("verify-site", {
334
+ description: "Trigger Google to validate a placed verification token.",
335
+ inputSchema: z.object({
336
+ siteUrl: z.string(),
337
+ method: verificationMethodSchema
338
+ }).shape
339
+ }, async ({ siteUrl, method }) => {
340
+ const resource = await verifySite(await getClient(), siteUrl, method);
341
+ return { content: [{
342
+ type: "text",
343
+ text: JSON.stringify({
344
+ siteUrl,
345
+ method,
346
+ resource
347
+ }, null, 2)
348
+ }] };
349
+ });
350
+ server.registerTool("list-verified-sites", {
351
+ description: "List verified WebResources from the Site Verification API.",
352
+ inputSchema: z.object({}).shape
353
+ }, async () => {
354
+ const resources = await listVerifiedSites(await getClient());
355
+ return { content: [{
356
+ type: "text",
357
+ text: JSON.stringify(resources, null, 2)
358
+ }] };
359
+ });
360
+ server.registerTool("get-verified-site", {
361
+ description: "Fetch a single verified WebResource by id.",
362
+ inputSchema: z.object({ id: z.string().describe("WebResource id (from list-verified-sites)") }).shape
363
+ }, async ({ id }) => {
364
+ const resource = await getVerifiedSite(await getClient(), id);
365
+ return { content: [{
366
+ type: "text",
367
+ text: JSON.stringify(resource, null, 2)
368
+ }] };
369
+ });
370
+ server.registerTool("unverify-site", {
371
+ description: "Drop the calling user's verified ownership of a WebResource. Remove the placed token first or Google may re-verify.",
372
+ inputSchema: z.object({ id: z.string().describe("WebResource id (from list-verified-sites)") }).shape
373
+ }, async ({ id }) => {
374
+ await unverifySite(await getClient(), id);
375
+ return { content: [{
376
+ type: "text",
377
+ text: JSON.stringify({
378
+ id,
379
+ status: "unverified"
380
+ }, null, 2)
381
+ }] };
382
+ });
383
+ server.registerTool("discover-sitemap", {
384
+ description: "Probe a domain's robots.txt + common paths for an advertised sitemap (no auth).",
385
+ inputSchema: z.object({ domain: z.string().describe("Domain (e.g., example.com)") }).shape
386
+ }, async ({ domain }) => {
387
+ const cleaned = String(domain).replace(/^https?:\/\//, "").replace(/\/.*$/, "");
388
+ const url = await discoverSitemap(cleaned).catch(() => null);
389
+ return { content: [{
390
+ type: "text",
391
+ text: JSON.stringify({
392
+ domain: cleaned,
393
+ sitemap: url
394
+ }, null, 2)
395
+ }] };
396
+ });
397
+ server.registerTool("batch-get-indexing-status", {
398
+ description: "Get indexing notification metadata for multiple URLs.",
399
+ inputSchema: z.object({
400
+ urls: z.array(z.string()).describe("URLs"),
401
+ delayMs: z.number().optional().describe("Delay between requests in ms (default 100)"),
402
+ concurrency: z.number().optional().describe("Concurrent in-flight requests (default 1)")
403
+ }).shape
404
+ }, async ({ urls, delayMs, concurrency }) => {
405
+ const client = await getClient();
406
+ const results = await runSequentialBatch(urls, (url) => getIndexingMetadata(client, url), {
407
+ delayMs: delayMs ?? 100,
408
+ concurrency: concurrency ?? 1
409
+ });
410
+ return { content: [{
411
+ type: "text",
412
+ text: JSON.stringify(results, null, 2)
413
+ }] };
414
+ });
415
+ return server;
416
+ }
417
+ export { McpServer, createGscMcpServer, runMcpSearchAnalyticsQuery };