@gscdump/cli 0.24.0 → 0.25.0
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/dist/index.mjs +705 -13
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -6,10 +6,9 @@ import fs, { readFile, readdir, rm } from "node:fs/promises";
|
|
|
6
6
|
import path, { join } from "node:path";
|
|
7
7
|
import { AnalyzerCapabilityError, createEngineQuerySource, runAnalyzerFromSource } from "@gscdump/analysis";
|
|
8
8
|
import { createGscApiQuerySource } from "@gscdump/engine-gsc-api";
|
|
9
|
-
import { decodeSiteId, normalizeSiteUrl } from "gscdump
|
|
9
|
+
import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, daysAgo, decodeSiteId, deleteSite, discoverSitemap, fetchSitemap, fetchSitemapUrls, fetchSitesWithSitemaps, formatErrorForCli, getDateRange, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, listVerifiedSites, normalizeSiteUrl, progressBar, requestIndexing, runSequentialBatch, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump";
|
|
10
10
|
import os from "node:os";
|
|
11
11
|
import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
12
|
-
import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, daysAgo, deleteSite, discoverSitemap, fetchSitemap, fetchSitemapUrls, fetchSitesWithSitemaps, formatErrorForCli, getDateRange, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, listVerifiedSites, progressBar, requestIndexing, runSequentialBatch, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump";
|
|
13
12
|
import { createServer } from "node:http";
|
|
14
13
|
import { JWT, OAuth2Client } from "google-auth-library";
|
|
15
14
|
import { ofetch } from "ofetch";
|
|
@@ -23,8 +22,9 @@ import { allTables, inferTable } from "@gscdump/engine/schema";
|
|
|
23
22
|
import { DuckDBInstance } from "@duckdb/node-api";
|
|
24
23
|
import { sqlEscape } from "@gscdump/engine/sql";
|
|
25
24
|
import { createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createSitemapStore } from "@gscdump/engine/entities";
|
|
26
|
-
import { createGscMcpServer } from "@gscdump/mcp/server";
|
|
27
25
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
27
|
+
import { z } from "zod";
|
|
28
28
|
import { defaultReportRegistry, dryRunReport, formatReport, runReport } from "@gscdump/analysis/report";
|
|
29
29
|
import { resolveWindow } from "@gscdump/engine/period";
|
|
30
30
|
import { inferLegacyTier } from "@gscdump/engine";
|
|
@@ -122,7 +122,7 @@ function loadEnvFromCwd() {
|
|
|
122
122
|
}
|
|
123
123
|
return applied;
|
|
124
124
|
}
|
|
125
|
-
var version = "0.
|
|
125
|
+
var version = "0.25.0";
|
|
126
126
|
const ALL_SEARCH_TYPES$1 = Object.values(SearchTypes);
|
|
127
127
|
const VERSION = version;
|
|
128
128
|
function noSubcommandSelected(parent, subNames) {
|
|
@@ -1659,7 +1659,7 @@ const AUTH_SUBCOMMANDS = [
|
|
|
1659
1659
|
"refresh",
|
|
1660
1660
|
"scopes"
|
|
1661
1661
|
];
|
|
1662
|
-
const REQUIRED_SCOPES$
|
|
1662
|
+
const REQUIRED_SCOPES$2 = [
|
|
1663
1663
|
"https://www.googleapis.com/auth/webmasters",
|
|
1664
1664
|
"https://www.googleapis.com/auth/indexing",
|
|
1665
1665
|
"https://www.googleapis.com/auth/siteverification"
|
|
@@ -1677,7 +1677,7 @@ async function resolveLiveAuthState() {
|
|
|
1677
1677
|
const tokenInfo = liveToken ? await fetchTokenInfo(liveToken) : null;
|
|
1678
1678
|
const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
|
|
1679
1679
|
const has = (s) => scopes.includes(s) || scopes.includes(s.replace(".readonly", ""));
|
|
1680
|
-
const missing = REQUIRED_SCOPES$
|
|
1680
|
+
const missing = REQUIRED_SCOPES$2.filter((s) => !has(s));
|
|
1681
1681
|
return {
|
|
1682
1682
|
byok,
|
|
1683
1683
|
tokens,
|
|
@@ -2147,13 +2147,13 @@ const configCommand = defineCommand({
|
|
|
2147
2147
|
});
|
|
2148
2148
|
}
|
|
2149
2149
|
});
|
|
2150
|
-
const REQUIRED_SCOPES = [
|
|
2150
|
+
const REQUIRED_SCOPES$1 = [
|
|
2151
2151
|
"https://www.googleapis.com/auth/webmasters",
|
|
2152
2152
|
"https://www.googleapis.com/auth/indexing",
|
|
2153
2153
|
"https://www.googleapis.com/auth/siteverification"
|
|
2154
2154
|
];
|
|
2155
|
-
const FETCH_TIMEOUT_MS = 5e3;
|
|
2156
|
-
const TIME_SKEW_WARN_MS = 5 * 6e4;
|
|
2155
|
+
const FETCH_TIMEOUT_MS$1 = 5e3;
|
|
2156
|
+
const TIME_SKEW_WARN_MS$1 = 5 * 6e4;
|
|
2157
2157
|
const WATERMARK_STALE_DAYS_WARN = 7;
|
|
2158
2158
|
const RELEVANT_ENV_KEYS = [
|
|
2159
2159
|
"GSC_ACCESS_TOKEN",
|
|
@@ -2279,7 +2279,7 @@ async function checkAuth$1(envKeys) {
|
|
|
2279
2279
|
detail: info.email
|
|
2280
2280
|
});
|
|
2281
2281
|
const scopes = info.scope ? info.scope.split(/\s+/) : [];
|
|
2282
|
-
const missing = REQUIRED_SCOPES.filter((s) => !scopes.includes(s) && !scopes.includes(s.replace(".readonly", "")));
|
|
2282
|
+
const missing = REQUIRED_SCOPES$1.filter((s) => !scopes.includes(s) && !scopes.includes(s.replace(".readonly", "")));
|
|
2283
2283
|
if (missing.length > 0) checks.push({
|
|
2284
2284
|
name: "auth.scopes",
|
|
2285
2285
|
status: "warn",
|
|
@@ -2311,7 +2311,7 @@ async function checkAuth$1(envKeys) {
|
|
|
2311
2311
|
async function checkTimeSkew() {
|
|
2312
2312
|
const dateHeader = await ofetch.raw("https://oauth2.googleapis.com/tokeninfo", {
|
|
2313
2313
|
method: "GET",
|
|
2314
|
-
timeout: FETCH_TIMEOUT_MS
|
|
2314
|
+
timeout: FETCH_TIMEOUT_MS$1
|
|
2315
2315
|
}).then((r) => r.headers.get("date")).catch((e) => e?.response?.headers?.get("date") ?? null);
|
|
2316
2316
|
if (!dateHeader) return [{
|
|
2317
2317
|
name: "time",
|
|
@@ -2326,7 +2326,7 @@ async function checkTimeSkew() {
|
|
|
2326
2326
|
}];
|
|
2327
2327
|
const skewMs = Date.now() - remoteMs;
|
|
2328
2328
|
const human = `${skewMs >= 0 ? "+" : ""}${(skewMs / 1e3).toFixed(1)}s`;
|
|
2329
|
-
if (Math.abs(skewMs) > TIME_SKEW_WARN_MS) return [{
|
|
2329
|
+
if (Math.abs(skewMs) > TIME_SKEW_WARN_MS$1) return [{
|
|
2330
2330
|
name: "time",
|
|
2331
2331
|
status: "warn",
|
|
2332
2332
|
detail: `local clock ${human} off Google — OAuth refresh may reject; sync your clock`
|
|
@@ -2415,7 +2415,7 @@ async function checkStoreWatermarks() {
|
|
|
2415
2415
|
async function checkApiReachable(name, url) {
|
|
2416
2416
|
const reachable = await ofetch.raw(url, {
|
|
2417
2417
|
method: "GET",
|
|
2418
|
-
timeout: FETCH_TIMEOUT_MS
|
|
2418
|
+
timeout: FETCH_TIMEOUT_MS$1
|
|
2419
2419
|
}).then(() => true).catch(() => false);
|
|
2420
2420
|
return [{
|
|
2421
2421
|
name,
|
|
@@ -3627,6 +3627,698 @@ const inspectCommand = defineCommand({
|
|
|
3627
3627
|
printInspection(args.url, inspection);
|
|
3628
3628
|
}
|
|
3629
3629
|
});
|
|
3630
|
+
const REQUIRED_SCOPES = [
|
|
3631
|
+
"https://www.googleapis.com/auth/webmasters",
|
|
3632
|
+
"https://www.googleapis.com/auth/indexing",
|
|
3633
|
+
"https://www.googleapis.com/auth/siteverification"
|
|
3634
|
+
];
|
|
3635
|
+
const FETCH_TIMEOUT_MS = 5e3;
|
|
3636
|
+
const TIME_SKEW_WARN_MS = 5 * 6e4;
|
|
3637
|
+
async function diagnostics(_input, ctx) {
|
|
3638
|
+
const checks = [];
|
|
3639
|
+
const token = await resolveAccessToken(ctx.auth);
|
|
3640
|
+
if (!token) {
|
|
3641
|
+
checks.push({
|
|
3642
|
+
name: "auth",
|
|
3643
|
+
status: "fail",
|
|
3644
|
+
detail: "no usable access token (refresh failed or no credentials)"
|
|
3645
|
+
});
|
|
3646
|
+
return {
|
|
3647
|
+
ok: false,
|
|
3648
|
+
checks
|
|
3649
|
+
};
|
|
3650
|
+
}
|
|
3651
|
+
const [tokenInfo, timeCheck, gscReachable, indexingReachable, sites] = await Promise.all([
|
|
3652
|
+
ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: token } }).catch((e) => ({ error: e.message })),
|
|
3653
|
+
probeTimeSkew(),
|
|
3654
|
+
probeReachable("https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
|
|
3655
|
+
probeReachable("https://indexing.googleapis.com/$discovery/rest?version=v3"),
|
|
3656
|
+
ctx.client.sites().catch((e) => e)
|
|
3657
|
+
]);
|
|
3658
|
+
if ("error" in tokenInfo) checks.push({
|
|
3659
|
+
name: "auth",
|
|
3660
|
+
status: "fail",
|
|
3661
|
+
detail: `tokeninfo failed: ${tokenInfo.error}`
|
|
3662
|
+
});
|
|
3663
|
+
else {
|
|
3664
|
+
checks.push({
|
|
3665
|
+
name: "auth",
|
|
3666
|
+
status: "pass",
|
|
3667
|
+
detail: tokenInfo.email ?? "token valid"
|
|
3668
|
+
});
|
|
3669
|
+
const scopes = tokenInfo.scope ? tokenInfo.scope.split(/\s+/) : [];
|
|
3670
|
+
const missing = REQUIRED_SCOPES.filter((s) => !scopes.includes(s));
|
|
3671
|
+
checks.push(missing.length > 0 ? {
|
|
3672
|
+
name: "auth.scopes",
|
|
3673
|
+
status: "warn",
|
|
3674
|
+
detail: `missing: ${missing.join(", ")}`
|
|
3675
|
+
} : {
|
|
3676
|
+
name: "auth.scopes",
|
|
3677
|
+
status: "pass",
|
|
3678
|
+
detail: `${scopes.length} granted`
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
checks.push(timeCheck);
|
|
3682
|
+
checks.push({
|
|
3683
|
+
name: "gsc.api",
|
|
3684
|
+
status: gscReachable ? "pass" : "warn",
|
|
3685
|
+
detail: gscReachable ? "reachable" : "searchconsole.googleapis.com unreachable"
|
|
3686
|
+
});
|
|
3687
|
+
checks.push({
|
|
3688
|
+
name: "indexing.api",
|
|
3689
|
+
status: indexingReachable ? "pass" : "warn",
|
|
3690
|
+
detail: indexingReachable ? "reachable" : "indexing.googleapis.com unreachable"
|
|
3691
|
+
});
|
|
3692
|
+
if (sites instanceof Error) checks.push({
|
|
3693
|
+
name: "gsc.sites",
|
|
3694
|
+
status: "fail",
|
|
3695
|
+
detail: `sites() failed: ${sites.message}`
|
|
3696
|
+
});
|
|
3697
|
+
else {
|
|
3698
|
+
const verified = sites.filter((s) => s.permissionLevel !== "siteUnverifiedUser").length;
|
|
3699
|
+
checks.push({
|
|
3700
|
+
name: "gsc.sites",
|
|
3701
|
+
status: "pass",
|
|
3702
|
+
detail: `${sites.length} site(s) accessible (${verified} verified)`
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
ok: !checks.some((c) => c.status === "fail"),
|
|
3707
|
+
checks
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
async function resolveAccessToken(auth) {
|
|
3711
|
+
if (typeof auth === "string") return auth;
|
|
3712
|
+
if (auth && typeof auth === "object" && "getAccessToken" in auth) return (await auth.getAccessToken().catch(() => null))?.token ?? null;
|
|
3713
|
+
return null;
|
|
3714
|
+
}
|
|
3715
|
+
async function probeTimeSkew() {
|
|
3716
|
+
const dateHeader = await ofetch.raw("https://oauth2.googleapis.com/tokeninfo", {
|
|
3717
|
+
method: "GET",
|
|
3718
|
+
timeout: FETCH_TIMEOUT_MS
|
|
3719
|
+
}).then((r) => r.headers.get("date")).catch((e) => e?.response?.headers?.get("date") ?? null);
|
|
3720
|
+
if (!dateHeader) return {
|
|
3721
|
+
name: "time",
|
|
3722
|
+
status: "warn",
|
|
3723
|
+
detail: "no Date header"
|
|
3724
|
+
};
|
|
3725
|
+
const remoteMs = Date.parse(dateHeader);
|
|
3726
|
+
if (!Number.isFinite(remoteMs)) return {
|
|
3727
|
+
name: "time",
|
|
3728
|
+
status: "warn",
|
|
3729
|
+
detail: `unparseable Date header: ${dateHeader}`
|
|
3730
|
+
};
|
|
3731
|
+
const skewMs = Date.now() - remoteMs;
|
|
3732
|
+
const human = `${skewMs >= 0 ? "+" : ""}${(skewMs / 1e3).toFixed(1)}s`;
|
|
3733
|
+
if (Math.abs(skewMs) > TIME_SKEW_WARN_MS) return {
|
|
3734
|
+
name: "time",
|
|
3735
|
+
status: "warn",
|
|
3736
|
+
detail: `local clock ${human} off Google`
|
|
3737
|
+
};
|
|
3738
|
+
return {
|
|
3739
|
+
name: "time",
|
|
3740
|
+
status: "pass",
|
|
3741
|
+
detail: `in sync (${human})`
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
async function probeReachable(url) {
|
|
3745
|
+
return ofetch.raw(url, {
|
|
3746
|
+
method: "GET",
|
|
3747
|
+
timeout: FETCH_TIMEOUT_MS
|
|
3748
|
+
}).then(() => true).catch(() => false);
|
|
3749
|
+
}
|
|
3750
|
+
async function requestIndexing$1(input, ctx) {
|
|
3751
|
+
return requestIndexing(ctx.client, input.url, { type: input.type || "URL_UPDATED" }).catch((e) => ({
|
|
3752
|
+
url: input.url,
|
|
3753
|
+
type: input.type || "URL_UPDATED",
|
|
3754
|
+
error: e.message
|
|
3755
|
+
}));
|
|
3756
|
+
}
|
|
3757
|
+
async function getIndexingStatus(input, ctx) {
|
|
3758
|
+
return getIndexingMetadata(ctx.client, input.url).catch((e) => ({
|
|
3759
|
+
url: input.url,
|
|
3760
|
+
error: e.message
|
|
3761
|
+
}));
|
|
3762
|
+
}
|
|
3763
|
+
async function batchRequestIndexing$1(input, ctx) {
|
|
3764
|
+
const results = await batchRequestIndexing(ctx.client, input.urls, {
|
|
3765
|
+
type: input.type || "URL_UPDATED",
|
|
3766
|
+
delayMs: input.delayMs || 100
|
|
3767
|
+
});
|
|
3768
|
+
return {
|
|
3769
|
+
results,
|
|
3770
|
+
success: results.length,
|
|
3771
|
+
failed: 0
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
async function batchInspectUrls$1(input, ctx) {
|
|
3775
|
+
const results = await batchInspectUrls(ctx.client, input.siteUrl, input.urls, { delayMs: input.delayMs || 200 });
|
|
3776
|
+
return {
|
|
3777
|
+
results,
|
|
3778
|
+
indexed: results.filter((r) => r.isIndexed).length,
|
|
3779
|
+
notIndexed: results.filter((r) => !r.isIndexed).length
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
const PERIOD_ALIASES$1 = {
|
|
3783
|
+
"7d": "last-7d",
|
|
3784
|
+
"28d": "last-28d",
|
|
3785
|
+
"30d": "last-30d",
|
|
3786
|
+
"90d": "last-90d",
|
|
3787
|
+
"180d": "last-180d",
|
|
3788
|
+
"365d": "last-365d",
|
|
3789
|
+
"mtd": "mtd",
|
|
3790
|
+
"ytd": "ytd",
|
|
3791
|
+
"custom": "custom"
|
|
3792
|
+
};
|
|
3793
|
+
const COMPARISON_ALIASES$1 = {
|
|
3794
|
+
"none": "none",
|
|
3795
|
+
"prev": "prev-period",
|
|
3796
|
+
"prev-period": "prev-period",
|
|
3797
|
+
"prior": "prev-period",
|
|
3798
|
+
"yoy": "yoy"
|
|
3799
|
+
};
|
|
3800
|
+
function listReports() {
|
|
3801
|
+
return defaultReportRegistry.listReports().map((r) => ({
|
|
3802
|
+
id: r.id,
|
|
3803
|
+
description: r.description,
|
|
3804
|
+
defaultPeriod: r.defaultPeriod,
|
|
3805
|
+
defaultComparison: r.defaultComparison,
|
|
3806
|
+
argsSpec: r.argsSpec
|
|
3807
|
+
}));
|
|
3808
|
+
}
|
|
3809
|
+
async function runReportHandler(input, ctx) {
|
|
3810
|
+
const report = defaultReportRegistry.getReport(input.id);
|
|
3811
|
+
if (!report) throw new Error(`Unknown report id "${input.id}". Available: ${defaultReportRegistry.listReportIds().join(", ")}`);
|
|
3812
|
+
const preset = input.period ? PERIOD_ALIASES$1[input.period.toLowerCase()] ?? null : report.defaultPeriod;
|
|
3813
|
+
if (!preset) throw new Error(`Unknown period "${input.period}". Supported: 7d, 28d, 30d, 90d, 180d, 365d, mtd, ytd, custom.`);
|
|
3814
|
+
const comparison = input.comparison ? COMPARISON_ALIASES$1[input.comparison.toLowerCase()] ?? null : report.defaultComparison;
|
|
3815
|
+
if (!comparison) throw new Error(`Unknown comparison "${input.comparison}". Supported: none, prev-period, yoy.`);
|
|
3816
|
+
const window = resolveWindow({
|
|
3817
|
+
preset,
|
|
3818
|
+
comparison,
|
|
3819
|
+
start: input.start,
|
|
3820
|
+
end: input.end
|
|
3821
|
+
});
|
|
3822
|
+
if (input.prevStart && input.prevEnd) window.comparison = {
|
|
3823
|
+
start: input.prevStart,
|
|
3824
|
+
end: input.prevEnd
|
|
3825
|
+
};
|
|
3826
|
+
const params = {};
|
|
3827
|
+
if (input.maxFindings != null) params.maxFindings = input.maxFindings;
|
|
3828
|
+
return runReport(report, {
|
|
3829
|
+
source: createGscApiQuerySource({
|
|
3830
|
+
client: ctx.client,
|
|
3831
|
+
siteUrl: input.siteUrl
|
|
3832
|
+
}),
|
|
3833
|
+
analyzers: defaultAnalyzerRegistry,
|
|
3834
|
+
ctx: {
|
|
3835
|
+
site: input.siteUrl,
|
|
3836
|
+
window,
|
|
3837
|
+
params,
|
|
3838
|
+
registryVersion: defaultReportRegistry.version
|
|
3839
|
+
}
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
async function listSitesWithSitemaps(_input, ctx) {
|
|
3843
|
+
return fetchSitesWithSitemaps(ctx.client);
|
|
3844
|
+
}
|
|
3845
|
+
async function getSitemap(input, ctx) {
|
|
3846
|
+
return fetchSitemap(ctx.client, input.siteUrl, input.feedpath);
|
|
3847
|
+
}
|
|
3848
|
+
const periodSchema = z.object({
|
|
3849
|
+
start: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
3850
|
+
end: z.string().describe("End date (YYYY-MM-DD)")
|
|
3851
|
+
}).describe("Date range for the query");
|
|
3852
|
+
const siteUrlSchema = z.string().describe("GSC property URL (e.g., sc-domain:example.com or https://example.com/)");
|
|
3853
|
+
const queryOptionsSchema = z.object({
|
|
3854
|
+
type: z.enum([
|
|
3855
|
+
"web",
|
|
3856
|
+
"image",
|
|
3857
|
+
"video",
|
|
3858
|
+
"news",
|
|
3859
|
+
"discover",
|
|
3860
|
+
"googleNews"
|
|
3861
|
+
]).optional().describe("Data type"),
|
|
3862
|
+
dataState: z.enum(["final", "all"]).optional().describe("Data state: final (settled) or all (includes fresh)"),
|
|
3863
|
+
aggregationType: z.enum(["byPage", "byProperty"]).optional().describe("Aggregation: byPage or byProperty")
|
|
3864
|
+
}).optional();
|
|
3865
|
+
const listSitesInput = z.object({});
|
|
3866
|
+
const listSitemapsInput = z.object({ siteUrl: siteUrlSchema });
|
|
3867
|
+
z.object({
|
|
3868
|
+
siteUrl: siteUrlSchema,
|
|
3869
|
+
period: periodSchema,
|
|
3870
|
+
comparePrevious: z.boolean().optional().describe("Include previous period comparison"),
|
|
3871
|
+
options: queryOptionsSchema
|
|
3872
|
+
});
|
|
3873
|
+
z.object({
|
|
3874
|
+
siteUrl: siteUrlSchema,
|
|
3875
|
+
period: periodSchema,
|
|
3876
|
+
url: z.string().describe("Page URL to fetch details for")
|
|
3877
|
+
});
|
|
3878
|
+
z.object({
|
|
3879
|
+
siteUrl: siteUrlSchema,
|
|
3880
|
+
period: periodSchema,
|
|
3881
|
+
keyword: z.string().describe("Keyword to fetch details for")
|
|
3882
|
+
});
|
|
3883
|
+
const inspectUrlInput = z.object({
|
|
3884
|
+
siteUrl: siteUrlSchema,
|
|
3885
|
+
inspectionUrl: z.string().describe("URL to inspect")
|
|
3886
|
+
});
|
|
3887
|
+
const requestIndexingInput = z.object({
|
|
3888
|
+
url: z.string().describe("URL to request indexing for"),
|
|
3889
|
+
type: z.enum(["URL_UPDATED", "URL_DELETED"]).optional().describe("Notification type")
|
|
3890
|
+
});
|
|
3891
|
+
const getIndexingStatusInput = z.object({ url: z.string().describe("URL to get indexing status for") });
|
|
3892
|
+
z.object({
|
|
3893
|
+
siteUrl: siteUrlSchema,
|
|
3894
|
+
period: periodSchema,
|
|
3895
|
+
dimensions: z.array(z.enum([
|
|
3896
|
+
"date",
|
|
3897
|
+
"query",
|
|
3898
|
+
"page",
|
|
3899
|
+
"country",
|
|
3900
|
+
"device",
|
|
3901
|
+
"searchAppearance"
|
|
3902
|
+
])).describe("Dimensions to group by"),
|
|
3903
|
+
rowLimit: z.number().optional().describe("Max rows (default 25000)"),
|
|
3904
|
+
options: queryOptionsSchema
|
|
3905
|
+
});
|
|
3906
|
+
const sitemapInput = z.object({
|
|
3907
|
+
siteUrl: siteUrlSchema,
|
|
3908
|
+
feedpath: z.string().describe("Sitemap URL (e.g., https://example.com/sitemap.xml)")
|
|
3909
|
+
});
|
|
3910
|
+
const batchRequestIndexingInput = z.object({
|
|
3911
|
+
urls: z.array(z.string()).describe("URLs to request indexing for"),
|
|
3912
|
+
type: z.enum(["URL_UPDATED", "URL_DELETED"]).optional().describe("Notification type"),
|
|
3913
|
+
delayMs: z.number().optional().describe("Delay between requests in ms (default 100)")
|
|
3914
|
+
});
|
|
3915
|
+
const batchInspectUrlsInput = z.object({
|
|
3916
|
+
siteUrl: siteUrlSchema,
|
|
3917
|
+
urls: z.array(z.string()).describe("URLs to inspect"),
|
|
3918
|
+
delayMs: z.number().optional().describe("Delay between requests in ms (default 200)")
|
|
3919
|
+
});
|
|
3920
|
+
const listReportsInput = z.object({});
|
|
3921
|
+
const runReportInput = z.object({
|
|
3922
|
+
siteUrl: siteUrlSchema,
|
|
3923
|
+
id: z.string().describe("Report id (e.g. health, movers, opportunities, risks). See list-reports."),
|
|
3924
|
+
period: z.string().optional().describe("Window: 7d|28d|30d|90d|180d|365d|mtd|ytd|custom (default per report)."),
|
|
3925
|
+
comparison: z.string().optional().describe("Comparison: none|prev-period|yoy (default per report)."),
|
|
3926
|
+
start: z.string().optional().describe("Custom window start (YYYY-MM-DD); requires period=custom."),
|
|
3927
|
+
end: z.string().optional().describe("Custom window end (YYYY-MM-DD); requires period=custom."),
|
|
3928
|
+
prevStart: z.string().optional().describe("Override comparison-window start."),
|
|
3929
|
+
prevEnd: z.string().optional().describe("Override comparison-window end."),
|
|
3930
|
+
maxFindings: z.number().optional().describe("Cap findings per section (per-report default ~5).")
|
|
3931
|
+
});
|
|
3932
|
+
function createGscMcpServer(options) {
|
|
3933
|
+
const { name = "gscdump", version = "1.0.0", getAuth } = options;
|
|
3934
|
+
const server = new McpServer({
|
|
3935
|
+
name,
|
|
3936
|
+
version
|
|
3937
|
+
});
|
|
3938
|
+
const auth = async () => Promise.resolve(getAuth());
|
|
3939
|
+
const getContext = async () => {
|
|
3940
|
+
const a = await auth();
|
|
3941
|
+
return {
|
|
3942
|
+
auth: a,
|
|
3943
|
+
client: googleSearchConsole(a)
|
|
3944
|
+
};
|
|
3945
|
+
};
|
|
3946
|
+
const getClient = async () => googleSearchConsole(await auth());
|
|
3947
|
+
server.registerTool("list-sites", {
|
|
3948
|
+
description: "List all Google Search Console sites visible to the authenticated user.",
|
|
3949
|
+
inputSchema: listSitesInput.shape
|
|
3950
|
+
}, async () => {
|
|
3951
|
+
const sites = (await (await getClient()).sites()).filter((s) => s.siteUrl && s.permissionLevel !== "siteUnverifiedUser").map((s) => ({
|
|
3952
|
+
siteUrl: s.siteUrl,
|
|
3953
|
+
permissionLevel: s.permissionLevel || "unknown"
|
|
3954
|
+
}));
|
|
3955
|
+
return { content: [{
|
|
3956
|
+
type: "text",
|
|
3957
|
+
text: JSON.stringify(sites, null, 2)
|
|
3958
|
+
}] };
|
|
3959
|
+
});
|
|
3960
|
+
server.registerTool("list-sites-with-sitemaps", {
|
|
3961
|
+
description: "List all GSC sites with their sitemaps",
|
|
3962
|
+
inputSchema: listSitesInput.shape
|
|
3963
|
+
}, async (args) => {
|
|
3964
|
+
const result = await listSitesWithSitemaps(args, await getContext());
|
|
3965
|
+
return { content: [{
|
|
3966
|
+
type: "text",
|
|
3967
|
+
text: JSON.stringify(result, null, 2)
|
|
3968
|
+
}] };
|
|
3969
|
+
});
|
|
3970
|
+
server.registerTool("list-sitemaps", {
|
|
3971
|
+
description: "List sitemaps for a specific site",
|
|
3972
|
+
inputSchema: listSitemapsInput.shape
|
|
3973
|
+
}, async (args) => {
|
|
3974
|
+
const sitemaps = await (await getClient()).sitemaps.list(args.siteUrl);
|
|
3975
|
+
return { content: [{
|
|
3976
|
+
type: "text",
|
|
3977
|
+
text: JSON.stringify(sitemaps, null, 2)
|
|
3978
|
+
}] };
|
|
3979
|
+
});
|
|
3980
|
+
server.registerTool("get-sitemap", {
|
|
3981
|
+
description: "Get details for a specific sitemap",
|
|
3982
|
+
inputSchema: sitemapInput.shape
|
|
3983
|
+
}, async (args) => {
|
|
3984
|
+
const result = await getSitemap(args, await getContext());
|
|
3985
|
+
return { content: [{
|
|
3986
|
+
type: "text",
|
|
3987
|
+
text: JSON.stringify(result, null, 2)
|
|
3988
|
+
}] };
|
|
3989
|
+
});
|
|
3990
|
+
server.registerTool("submit-sitemap", {
|
|
3991
|
+
description: "Submit a sitemap to Google Search Console",
|
|
3992
|
+
inputSchema: sitemapInput.shape
|
|
3993
|
+
}, async (args) => {
|
|
3994
|
+
await (await getClient()).sitemaps.submit(args.siteUrl, args.feedpath);
|
|
3995
|
+
return { content: [{
|
|
3996
|
+
type: "text",
|
|
3997
|
+
text: JSON.stringify({ success: true }, null, 2)
|
|
3998
|
+
}] };
|
|
3999
|
+
});
|
|
4000
|
+
server.registerTool("delete-sitemap", {
|
|
4001
|
+
description: "Delete a sitemap from Google Search Console",
|
|
4002
|
+
inputSchema: sitemapInput.shape
|
|
4003
|
+
}, async (args) => {
|
|
4004
|
+
await (await getClient()).sitemaps.delete(args.siteUrl, args.feedpath);
|
|
4005
|
+
return { content: [{
|
|
4006
|
+
type: "text",
|
|
4007
|
+
text: JSON.stringify({ success: true }, null, 2)
|
|
4008
|
+
}] };
|
|
4009
|
+
});
|
|
4010
|
+
server.registerTool("list-reports", {
|
|
4011
|
+
description: "List available reports (intent-keyed analyzer compositions). Returns id, description, default period/comparison, and per-report argsSpec.",
|
|
4012
|
+
inputSchema: listReportsInput.shape
|
|
4013
|
+
}, async () => {
|
|
4014
|
+
const result = listReports();
|
|
4015
|
+
return { content: [{
|
|
4016
|
+
type: "text",
|
|
4017
|
+
text: JSON.stringify(result, null, 2)
|
|
4018
|
+
}] };
|
|
4019
|
+
});
|
|
4020
|
+
server.registerTool("run-report", {
|
|
4021
|
+
description: "Run a report against the GSC API. Returns a structured ReportResult with bounded findings per section. See list-reports for ids.",
|
|
4022
|
+
inputSchema: runReportInput.shape
|
|
4023
|
+
}, async (args) => {
|
|
4024
|
+
const result = await runReportHandler(args, await getContext());
|
|
4025
|
+
return { content: [{
|
|
4026
|
+
type: "text",
|
|
4027
|
+
text: JSON.stringify(result, null, 2)
|
|
4028
|
+
}] };
|
|
4029
|
+
});
|
|
4030
|
+
const filterOperatorSchema = z.enum([
|
|
4031
|
+
"equals",
|
|
4032
|
+
"notEquals",
|
|
4033
|
+
"contains",
|
|
4034
|
+
"notContains",
|
|
4035
|
+
"includingRegex",
|
|
4036
|
+
"excludingRegex"
|
|
4037
|
+
]);
|
|
4038
|
+
const dimensionFilterSchema = z.object({
|
|
4039
|
+
dimension: z.enum([
|
|
4040
|
+
"date",
|
|
4041
|
+
"query",
|
|
4042
|
+
"page",
|
|
4043
|
+
"country",
|
|
4044
|
+
"device",
|
|
4045
|
+
"searchAppearance"
|
|
4046
|
+
]),
|
|
4047
|
+
operator: filterOperatorSchema,
|
|
4048
|
+
expression: z.string()
|
|
4049
|
+
});
|
|
4050
|
+
const filterGroupSchema = z.object({
|
|
4051
|
+
groupType: z.enum(["and"]).optional().describe("Always \"and\"; multiple groups are OR-ed together"),
|
|
4052
|
+
filters: z.array(dimensionFilterSchema)
|
|
4053
|
+
});
|
|
4054
|
+
server.registerTool("query", {
|
|
4055
|
+
description: "Run a custom search analytics query. Supports dimension filters (regex/contains/equals) via dimensionFilterGroups; multiple groups are OR-ed.",
|
|
4056
|
+
inputSchema: z.object({
|
|
4057
|
+
siteUrl: z.string().describe("GSC property URL (e.g., sc-domain:example.com)"),
|
|
4058
|
+
startDate: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
4059
|
+
endDate: z.string().describe("End date (YYYY-MM-DD)"),
|
|
4060
|
+
dimensions: z.array(z.enum([
|
|
4061
|
+
"date",
|
|
4062
|
+
"query",
|
|
4063
|
+
"page",
|
|
4064
|
+
"country",
|
|
4065
|
+
"device",
|
|
4066
|
+
"searchAppearance"
|
|
4067
|
+
])).describe("Dimensions to group by"),
|
|
4068
|
+
rowLimit: z.number().optional().describe("Max rows (default 25000)"),
|
|
4069
|
+
type: z.enum([
|
|
4070
|
+
"web",
|
|
4071
|
+
"image",
|
|
4072
|
+
"video",
|
|
4073
|
+
"news",
|
|
4074
|
+
"discover",
|
|
4075
|
+
"googleNews"
|
|
4076
|
+
]).optional().describe("Search type"),
|
|
4077
|
+
dataState: z.enum(["final", "all"]).optional().describe("Data state: final (settled) or all (includes fresh)"),
|
|
4078
|
+
aggregationType: z.enum(["byPage", "byProperty"]).optional().describe("Aggregation type"),
|
|
4079
|
+
dimensionFilterGroups: z.array(filterGroupSchema).optional().describe("Filter groups (each \"and\"-ed internally; multiple groups are OR-ed)")
|
|
4080
|
+
}).shape
|
|
4081
|
+
}, async ({ siteUrl, startDate, endDate, dimensions, rowLimit, type, dataState, aggregationType, dimensionFilterGroups }) => {
|
|
4082
|
+
const client = await getClient();
|
|
4083
|
+
const limit = rowLimit ?? 25e3;
|
|
4084
|
+
const allRows = [];
|
|
4085
|
+
let startRow = 0;
|
|
4086
|
+
while (true) {
|
|
4087
|
+
const rows = ((await client._rawQuery(siteUrl, {
|
|
4088
|
+
startDate,
|
|
4089
|
+
endDate,
|
|
4090
|
+
dimensions,
|
|
4091
|
+
rowLimit: limit,
|
|
4092
|
+
startRow,
|
|
4093
|
+
...type ? { type } : {},
|
|
4094
|
+
...dataState ? { dataState } : {},
|
|
4095
|
+
...aggregationType ? { aggregationType } : {},
|
|
4096
|
+
...dimensionFilterGroups ? { dimensionFilterGroups: dimensionFilterGroups.map((g) => ({
|
|
4097
|
+
groupType: g.groupType ?? "and",
|
|
4098
|
+
filters: g.filters
|
|
4099
|
+
})) } : {}
|
|
4100
|
+
})).rows || []).map((row) => {
|
|
4101
|
+
const result = {
|
|
4102
|
+
clicks: row.clicks ?? 0,
|
|
4103
|
+
impressions: row.impressions ?? 0,
|
|
4104
|
+
ctr: row.ctr ?? 0,
|
|
4105
|
+
position: row.position ?? 0
|
|
4106
|
+
};
|
|
4107
|
+
dimensions.forEach((dim, i) => {
|
|
4108
|
+
result[dim] = row.keys?.[i];
|
|
4109
|
+
});
|
|
4110
|
+
return result;
|
|
4111
|
+
});
|
|
4112
|
+
allRows.push(...rows);
|
|
4113
|
+
if (rows.length < limit) break;
|
|
4114
|
+
startRow += rows.length;
|
|
4115
|
+
}
|
|
4116
|
+
return { content: [{
|
|
4117
|
+
type: "text",
|
|
4118
|
+
text: JSON.stringify({
|
|
4119
|
+
siteUrl,
|
|
4120
|
+
rowCount: allRows.length,
|
|
4121
|
+
rows: allRows
|
|
4122
|
+
}, null, 2)
|
|
4123
|
+
}] };
|
|
4124
|
+
});
|
|
4125
|
+
server.registerTool("inspect-url", {
|
|
4126
|
+
description: "Inspect a URL to check its indexing status in Google Search Console",
|
|
4127
|
+
inputSchema: inspectUrlInput.shape
|
|
4128
|
+
}, async (args) => {
|
|
4129
|
+
const result = await (await getClient()).inspect(args.siteUrl, args.inspectionUrl);
|
|
4130
|
+
return { content: [{
|
|
4131
|
+
type: "text",
|
|
4132
|
+
text: JSON.stringify(result, null, 2)
|
|
4133
|
+
}] };
|
|
4134
|
+
});
|
|
4135
|
+
server.registerTool("request-indexing", {
|
|
4136
|
+
description: "Request Google to index or remove a URL via the Indexing API",
|
|
4137
|
+
inputSchema: requestIndexingInput.shape
|
|
4138
|
+
}, async (args) => {
|
|
4139
|
+
const result = await requestIndexing$1(args, await getContext());
|
|
4140
|
+
return { content: [{
|
|
4141
|
+
type: "text",
|
|
4142
|
+
text: JSON.stringify(result, null, 2)
|
|
4143
|
+
}] };
|
|
4144
|
+
});
|
|
4145
|
+
server.registerTool("get-indexing-status", {
|
|
4146
|
+
description: "Get indexing status metadata for a URL",
|
|
4147
|
+
inputSchema: getIndexingStatusInput.shape
|
|
4148
|
+
}, async (args) => {
|
|
4149
|
+
const result = await getIndexingStatus(args, await getContext());
|
|
4150
|
+
return { content: [{
|
|
4151
|
+
type: "text",
|
|
4152
|
+
text: JSON.stringify(result, null, 2)
|
|
4153
|
+
}] };
|
|
4154
|
+
});
|
|
4155
|
+
server.registerTool("batch-request-indexing", {
|
|
4156
|
+
description: "Batch request indexing for multiple URLs with rate limiting",
|
|
4157
|
+
inputSchema: batchRequestIndexingInput.shape
|
|
4158
|
+
}, async (args) => {
|
|
4159
|
+
const result = await batchRequestIndexing$1(args, await getContext());
|
|
4160
|
+
return { content: [{
|
|
4161
|
+
type: "text",
|
|
4162
|
+
text: JSON.stringify(result, null, 2)
|
|
4163
|
+
}] };
|
|
4164
|
+
});
|
|
4165
|
+
server.registerTool("batch-inspect-urls", {
|
|
4166
|
+
description: "Batch inspect multiple URLs to check their indexing status",
|
|
4167
|
+
inputSchema: batchInspectUrlsInput.shape
|
|
4168
|
+
}, async (args) => {
|
|
4169
|
+
const result = await batchInspectUrls$1(args, await getContext());
|
|
4170
|
+
return { content: [{
|
|
4171
|
+
type: "text",
|
|
4172
|
+
text: JSON.stringify(result, null, 2)
|
|
4173
|
+
}] };
|
|
4174
|
+
});
|
|
4175
|
+
server.registerTool("diagnostics", {
|
|
4176
|
+
description: "Run health checks on the active GSC connection: auth/scopes, time skew, API reachability, sites count.",
|
|
4177
|
+
inputSchema: listSitesInput.shape
|
|
4178
|
+
}, async (args) => {
|
|
4179
|
+
const result = await diagnostics(args, await getContext());
|
|
4180
|
+
return { content: [{
|
|
4181
|
+
type: "text",
|
|
4182
|
+
text: JSON.stringify(result, null, 2)
|
|
4183
|
+
}] };
|
|
4184
|
+
});
|
|
4185
|
+
server.registerTool("add-site", {
|
|
4186
|
+
description: "Register a property in Search Console (unverified). Verify ownership separately.",
|
|
4187
|
+
inputSchema: z.object({ siteUrl: z.string().describe("Property URL (https://example.com/ or sc-domain:example.com)") }).shape
|
|
4188
|
+
}, async ({ siteUrl }) => {
|
|
4189
|
+
await addSite(await getClient(), siteUrl);
|
|
4190
|
+
return { content: [{
|
|
4191
|
+
type: "text",
|
|
4192
|
+
text: JSON.stringify({
|
|
4193
|
+
siteUrl,
|
|
4194
|
+
status: "added",
|
|
4195
|
+
verified: false
|
|
4196
|
+
}, null, 2)
|
|
4197
|
+
}] };
|
|
4198
|
+
});
|
|
4199
|
+
server.registerTool("delete-site", {
|
|
4200
|
+
description: "Remove a property from Search Console.",
|
|
4201
|
+
inputSchema: z.object({ siteUrl: z.string().describe("Property URL") }).shape
|
|
4202
|
+
}, async ({ siteUrl }) => {
|
|
4203
|
+
await deleteSite(await getClient(), siteUrl);
|
|
4204
|
+
return { content: [{
|
|
4205
|
+
type: "text",
|
|
4206
|
+
text: JSON.stringify({
|
|
4207
|
+
siteUrl,
|
|
4208
|
+
status: "deleted"
|
|
4209
|
+
}, null, 2)
|
|
4210
|
+
}] };
|
|
4211
|
+
});
|
|
4212
|
+
const verificationMethodSchema = z.enum([
|
|
4213
|
+
"META",
|
|
4214
|
+
"FILE",
|
|
4215
|
+
"DNS_TXT",
|
|
4216
|
+
"DNS_CNAME",
|
|
4217
|
+
"ANALYTICS",
|
|
4218
|
+
"TAG_MANAGER"
|
|
4219
|
+
]);
|
|
4220
|
+
server.registerTool("get-verification-token", {
|
|
4221
|
+
description: "Get a verification token to place on the site or in DNS.",
|
|
4222
|
+
inputSchema: z.object({
|
|
4223
|
+
siteUrl: z.string(),
|
|
4224
|
+
method: verificationMethodSchema
|
|
4225
|
+
}).shape
|
|
4226
|
+
}, async ({ siteUrl, method }) => {
|
|
4227
|
+
const result = await getVerificationToken(await getClient(), siteUrl, method);
|
|
4228
|
+
return { content: [{
|
|
4229
|
+
type: "text",
|
|
4230
|
+
text: JSON.stringify({
|
|
4231
|
+
siteUrl,
|
|
4232
|
+
method,
|
|
4233
|
+
token: result.token,
|
|
4234
|
+
site: result.site
|
|
4235
|
+
}, null, 2)
|
|
4236
|
+
}] };
|
|
4237
|
+
});
|
|
4238
|
+
server.registerTool("verify-site", {
|
|
4239
|
+
description: "Trigger Google to validate a placed verification token.",
|
|
4240
|
+
inputSchema: z.object({
|
|
4241
|
+
siteUrl: z.string(),
|
|
4242
|
+
method: verificationMethodSchema
|
|
4243
|
+
}).shape
|
|
4244
|
+
}, async ({ siteUrl, method }) => {
|
|
4245
|
+
const resource = await verifySite(await getClient(), siteUrl, method);
|
|
4246
|
+
return { content: [{
|
|
4247
|
+
type: "text",
|
|
4248
|
+
text: JSON.stringify({
|
|
4249
|
+
siteUrl,
|
|
4250
|
+
method,
|
|
4251
|
+
resource
|
|
4252
|
+
}, null, 2)
|
|
4253
|
+
}] };
|
|
4254
|
+
});
|
|
4255
|
+
server.registerTool("list-verified-sites", {
|
|
4256
|
+
description: "List verified WebResources from the Site Verification API.",
|
|
4257
|
+
inputSchema: z.object({}).shape
|
|
4258
|
+
}, async () => {
|
|
4259
|
+
const resources = await listVerifiedSites(await getClient());
|
|
4260
|
+
return { content: [{
|
|
4261
|
+
type: "text",
|
|
4262
|
+
text: JSON.stringify(resources, null, 2)
|
|
4263
|
+
}] };
|
|
4264
|
+
});
|
|
4265
|
+
server.registerTool("get-verified-site", {
|
|
4266
|
+
description: "Fetch a single verified WebResource by id.",
|
|
4267
|
+
inputSchema: z.object({ id: z.string().describe("WebResource id (from list-verified-sites)") }).shape
|
|
4268
|
+
}, async ({ id }) => {
|
|
4269
|
+
const resource = await getVerifiedSite(await getClient(), id);
|
|
4270
|
+
return { content: [{
|
|
4271
|
+
type: "text",
|
|
4272
|
+
text: JSON.stringify(resource, null, 2)
|
|
4273
|
+
}] };
|
|
4274
|
+
});
|
|
4275
|
+
server.registerTool("unverify-site", {
|
|
4276
|
+
description: "Drop the calling user's verified ownership of a WebResource. Remove the placed token first or Google may re-verify.",
|
|
4277
|
+
inputSchema: z.object({ id: z.string().describe("WebResource id (from list-verified-sites)") }).shape
|
|
4278
|
+
}, async ({ id }) => {
|
|
4279
|
+
await unverifySite(await getClient(), id);
|
|
4280
|
+
return { content: [{
|
|
4281
|
+
type: "text",
|
|
4282
|
+
text: JSON.stringify({
|
|
4283
|
+
id,
|
|
4284
|
+
status: "unverified"
|
|
4285
|
+
}, null, 2)
|
|
4286
|
+
}] };
|
|
4287
|
+
});
|
|
4288
|
+
server.registerTool("discover-sitemap", {
|
|
4289
|
+
description: "Probe a domain's robots.txt + common paths for an advertised sitemap (no auth).",
|
|
4290
|
+
inputSchema: z.object({ domain: z.string().describe("Domain (e.g., example.com)") }).shape
|
|
4291
|
+
}, async ({ domain }) => {
|
|
4292
|
+
const cleaned = String(domain).replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
4293
|
+
const url = await discoverSitemap(cleaned).catch(() => null);
|
|
4294
|
+
return { content: [{
|
|
4295
|
+
type: "text",
|
|
4296
|
+
text: JSON.stringify({
|
|
4297
|
+
domain: cleaned,
|
|
4298
|
+
sitemap: url
|
|
4299
|
+
}, null, 2)
|
|
4300
|
+
}] };
|
|
4301
|
+
});
|
|
4302
|
+
server.registerTool("batch-get-indexing-status", {
|
|
4303
|
+
description: "Get indexing notification metadata for multiple URLs.",
|
|
4304
|
+
inputSchema: z.object({
|
|
4305
|
+
urls: z.array(z.string()).describe("URLs"),
|
|
4306
|
+
delayMs: z.number().optional().describe("Delay between requests in ms (default 100)"),
|
|
4307
|
+
concurrency: z.number().optional().describe("Concurrent in-flight requests (default 1)")
|
|
4308
|
+
}).shape
|
|
4309
|
+
}, async ({ urls, delayMs, concurrency }) => {
|
|
4310
|
+
const client = await getClient();
|
|
4311
|
+
const results = await runSequentialBatch(urls, (url) => getIndexingMetadata(client, url), {
|
|
4312
|
+
delayMs: delayMs ?? 100,
|
|
4313
|
+
concurrency: concurrency ?? 1
|
|
4314
|
+
});
|
|
4315
|
+
return { content: [{
|
|
4316
|
+
type: "text",
|
|
4317
|
+
text: JSON.stringify(results, null, 2)
|
|
4318
|
+
}] };
|
|
4319
|
+
});
|
|
4320
|
+
return server;
|
|
4321
|
+
}
|
|
3630
4322
|
async function checkAuth() {
|
|
3631
4323
|
if (resolveBYOK()) return { ok: true };
|
|
3632
4324
|
const config = await loadConfig();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.25.0",
|
|
5
5
|
"description": "CLI for Google Search Console - dump, query, and run MCP server",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"google-auth-library": "^10.6.2",
|
|
43
43
|
"ofetch": "^1.5.1",
|
|
44
44
|
"open": "^11.0.0",
|
|
45
|
-
"
|
|
46
|
-
"@gscdump/
|
|
47
|
-
"@gscdump/engine
|
|
48
|
-
"@gscdump/
|
|
49
|
-
"gscdump": "0.
|
|
45
|
+
"zod": "^4.4.3",
|
|
46
|
+
"@gscdump/analysis": "0.25.0",
|
|
47
|
+
"@gscdump/engine": "0.25.0",
|
|
48
|
+
"@gscdump/engine-gsc-api": "0.25.0",
|
|
49
|
+
"gscdump": "0.25.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@duckdb/node-api": "1.5.1-r.2",
|