@getmcpads/google-search-console-mcp-server 1.0.2 → 2.0.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/CHANGELOG.md +20 -0
- package/README.md +94 -57
- package/dist/cli.js +472 -122
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +480 -130
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
- package/server-card.json +2191 -0
- package/server.json +55 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,290 @@
|
|
|
1
|
+
// src/parameter-descriptions.ts
|
|
2
|
+
var PARAMETER_DESCRIPTIONS = {
|
|
3
|
+
"filters": "Filters combined according to this tool's query schema. Use provider field names and the declared operators.",
|
|
4
|
+
"searchType": "Search surface to report, such as web, image, video, news or Discover. Availability depends on the property.",
|
|
5
|
+
"searchTypes": "Search surfaces to compare. Each surface is queried separately; do not sum overlapping surfaces.",
|
|
6
|
+
"datePreset": "Named reporting period. Use the custom date range when you need exact start and end dates.",
|
|
7
|
+
"dateRange": "Explicit reporting start and end dates in YYYY-MM-DD format.",
|
|
8
|
+
"timeRange": "Explicit provider reporting period with since and until dates.",
|
|
9
|
+
"startDate": "First date of the reporting period, in YYYY-MM-DD format.",
|
|
10
|
+
"endDate": "Last date of the reporting period, in YYYY-MM-DD format, on or after startDate.",
|
|
11
|
+
"since": "Beginning of the requested reporting period; use the format accepted by the provider.",
|
|
12
|
+
"until": "End of the requested reporting period; use the format accepted by the provider.",
|
|
13
|
+
"orderBy": "Field used to order report rows.",
|
|
14
|
+
"orderField": "Provider field used to order report rows.",
|
|
15
|
+
"orderDirection": "Sort direction for the selected ordering field.",
|
|
16
|
+
"orderType": "Sort direction for the selected ordering field.",
|
|
17
|
+
"sortBy": "Metric or dimension used to rank the returned results.",
|
|
18
|
+
"sort": "Native provider ordering expressions.",
|
|
19
|
+
"limit": "Maximum number of returned items or rows. The declared bounds and default apply; use pagination for additional results.",
|
|
20
|
+
"topN": "Maximum number of highest-ranked results to return.",
|
|
21
|
+
"pageSize": "Maximum items requested per page. Follow the returned pagination information for remaining results.",
|
|
22
|
+
"page": "Page number to request, starting at 1.",
|
|
23
|
+
"offset": "Zero-based number of rows to skip before returning results.",
|
|
24
|
+
"bookmark": "Opaque Pinterest pagination bookmark returned by the previous response. Omit on the first request.",
|
|
25
|
+
"after": "Opaque cursor for the next page returned by Meta.",
|
|
26
|
+
"before": "Opaque cursor for the previous page returned by Meta.",
|
|
27
|
+
"includeUnchanged": "Include entries with no observed change in the comparison.",
|
|
28
|
+
"minImpressions": "Minimum reported impressions required for a row to enter the analysis.",
|
|
29
|
+
"objectives": "Analysis goals that determine the recommended large-site sampling strategy.",
|
|
30
|
+
"pageLimit": "Maximum number of pages considered for the sampling plan.",
|
|
31
|
+
"maxInspectionUrls": "Maximum URL Inspection candidates included in the plan; inspection calls consume provider quota.",
|
|
32
|
+
"minImpressionsForLowCtr": "Minimum impressions before a page is considered for low-CTR analysis.",
|
|
33
|
+
"staleSitemapDays": "Number of days after which a sitemap's last download is considered stale.",
|
|
34
|
+
"continueOnError": "Continue with other URLs when one inspection fails, returning individual failures.",
|
|
35
|
+
"dimension": "Reporting dimension used to group and compare results.",
|
|
36
|
+
"maxClusters": "Maximum query clusters to return.",
|
|
37
|
+
"topQueriesPerCluster": "Maximum example queries returned for each cluster.",
|
|
38
|
+
"minPages": "Minimum distinct pages ranking for a query before it is considered a cannibalization candidate.",
|
|
39
|
+
"adAccountId": "Owning advertising account ID. Use the exact ID returned by account discovery; do not substitute a campaign or business ID.",
|
|
40
|
+
"advertiserId": "TikTok advertiser account ID as a string.",
|
|
41
|
+
"customerId": "Google Ads customer ID, without hyphens.",
|
|
42
|
+
"propertyId": "Google Analytics property ID; use the property, not the account or measurement ID.",
|
|
43
|
+
"level": "Entity or aggregation level for the requested report.",
|
|
44
|
+
"dataLevel": "TikTok reporting aggregation level. Metrics and dimensions must be compatible with this level.",
|
|
45
|
+
"granularity": "Time bucket used to aggregate reporting rows.",
|
|
46
|
+
"columns": "Native Pinterest reporting column names. Discover supported columns before selecting metrics.",
|
|
47
|
+
"executionMode": "Choose the supported synchronous, asynchronous or automatic report execution strategy.",
|
|
48
|
+
"entityIds": "Exact IDs of the entities to include in the selected reporting level.",
|
|
49
|
+
"targetingTypes": "Native targeting categories used to break down delivery.",
|
|
50
|
+
"clickWindowDays": "Click-through attribution window, in days.",
|
|
51
|
+
"engagementWindowDays": "Engagement-through attribution window, in days.",
|
|
52
|
+
"viewWindowDays": "View-through attribution window, in days.",
|
|
53
|
+
"conversionReportTime": "Whether conversions are assigned to the ad action date or the conversion date.",
|
|
54
|
+
"attributionTypes": "Provider attribution categories included in the report.",
|
|
55
|
+
"reportingTimezone": "Provider reporting timezone selector. Keep it consistent when comparing periods.",
|
|
56
|
+
"campaignIds": "Restrict results to these campaign IDs.",
|
|
57
|
+
"adGroupIds": "Restrict results to these ad group IDs.",
|
|
58
|
+
"adgroupIds": "Restrict TikTok results to these ad group IDs.",
|
|
59
|
+
"adIds": "Restrict results to these ad IDs.",
|
|
60
|
+
"campaignId": "Exact campaign ID in the selected advertising account.",
|
|
61
|
+
"adGroupId": "Exact ad group ID in the selected advertising account.",
|
|
62
|
+
"adSetId": "Exact Meta ad set ID in the selected advertising account.",
|
|
63
|
+
"adId": "Exact ad ID in the selected advertising account.",
|
|
64
|
+
"entityId": "Exact ID of the entity selected by this operation. It must belong to the selected account.",
|
|
65
|
+
"onlyWithPeriodDelivery": "Keep only creatives with observed delivery in the requested date range.",
|
|
66
|
+
"breakdown": "Native reporting breakdown used to group results.",
|
|
67
|
+
"breakdowns": "Native reporting breakdowns. Check compatibility before combining them.",
|
|
68
|
+
"productGroupIds": "Restrict reporting to these product group IDs.",
|
|
69
|
+
"productItemIds": "Restrict reporting to these product item IDs.",
|
|
70
|
+
"includeProductSamples": "Include a limited sample of product records alongside inventory diagnostics.",
|
|
71
|
+
"sampleProductGroups": "Maximum product groups to sample for inventory diagnostics.",
|
|
72
|
+
"reportName": "Provider report name identifying the requested report recipe.",
|
|
73
|
+
"conversionProductAttributionType": "Pinterest product-conversion attribution category.",
|
|
74
|
+
"conversionProductBreakdown": "Pinterest product-conversion grouping level.",
|
|
75
|
+
"productSkuIds": "Restrict product-conversion reporting to these SKU IDs.",
|
|
76
|
+
"request": "Exact request schema name to inspect before composing a write payload.",
|
|
77
|
+
"entity": "Entity collection or level to inspect.",
|
|
78
|
+
"entityType": "Provider entity type to retrieve.",
|
|
79
|
+
"entityStatuses": "Native status values used to filter the entity collection.",
|
|
80
|
+
"statusFilter": "Native status values used to filter the returned entities.",
|
|
81
|
+
"targetingType": "Targeting dictionary to inspect, such as interests, locations or demographics.",
|
|
82
|
+
"interestId": "Native interest ID for the targeting lookup.",
|
|
83
|
+
"mode": "Operation or analysis mode. Only the values declared in this schema are supported.",
|
|
84
|
+
"matchTypes": "Keyword match types included in the lookup.",
|
|
85
|
+
"countryCode": "Country code used to localize keyword or targeting results.",
|
|
86
|
+
"keywords": "Keyword phrases to analyze; use the language and market of the intended audience.",
|
|
87
|
+
"term": "Search term used for the keyword lookup.",
|
|
88
|
+
"terms": "Search terms used for the keyword lookup.",
|
|
89
|
+
"audienceId": "Exact ID of the audience to inspect or modify.",
|
|
90
|
+
"customerListId": "Exact customer-list ID. Reading metadata does not upload customer data.",
|
|
91
|
+
"businessId": "Exact business account ID used for the lookup.",
|
|
92
|
+
"accountType": "Provider account category used to filter results.",
|
|
93
|
+
"ownershipType": "Filter audiences by provider ownership category.",
|
|
94
|
+
"excludeNca": "Whether to exclude Pinterest new-customer-acquisition audiences from this lookup.",
|
|
95
|
+
"audienceInsightType": "Pinterest audience-insights category to request.",
|
|
96
|
+
"surfaces": "Conversion-setup surfaces to inspect, according to the supported values.",
|
|
97
|
+
"includeDeletedTags": "Include deleted conversion tags in diagnostics.",
|
|
98
|
+
"lookbackPeriod": "Provider lookback period used for conversion diagnostics.",
|
|
99
|
+
"sourcePlatform": "Filter conversion data by source platform.",
|
|
100
|
+
"ingestionSource": "Filter conversion events by ingestion source.",
|
|
101
|
+
"catalogId": "Exact product catalog ID accessible to the selected account.",
|
|
102
|
+
"feedId": "Exact catalog feed ID.",
|
|
103
|
+
"processingResultId": "Exact catalog feed processing result ID to inspect.",
|
|
104
|
+
"productGroupId": "Exact product group ID.",
|
|
105
|
+
"productSetId": "Exact Meta product set ID belonging to the selected catalog.",
|
|
106
|
+
"exportType": "Provider export category to request.",
|
|
107
|
+
"action": "Supported operation within this tool's scoped provider surface.",
|
|
108
|
+
"includeDetails": "Include detailed provider records in addition to summary information.",
|
|
109
|
+
"leadFormId": "Exact lead form ID to inspect.",
|
|
110
|
+
"subscriptionId": "Exact subscription ID to inspect.",
|
|
111
|
+
"assetId": "Exact provider asset ID.",
|
|
112
|
+
"memberId": "Exact business member ID used for the read lookup.",
|
|
113
|
+
"partnerId": "Exact business partner ID used for the read lookup.",
|
|
114
|
+
"pinId": "Exact Pinterest pin ID.",
|
|
115
|
+
"pinIds": "Exact Pinterest pin IDs to include.",
|
|
116
|
+
"metrics": "Native or documented calculated metric names to request. Check compatibility with the selected dimensions.",
|
|
117
|
+
"metric": "Metric used for the analysis; use a name from the platform metric catalogue.",
|
|
118
|
+
"boardId": "Exact Pinterest board ID.",
|
|
119
|
+
"searchTerm": "Text used to filter or search the selected collection.",
|
|
120
|
+
"region": "Provider region selector used to localize trends.",
|
|
121
|
+
"productRegion": "Region used for product trend analysis.",
|
|
122
|
+
"trendType": "Provider trend category to return.",
|
|
123
|
+
"interests": "Native interest categories used to filter trends.",
|
|
124
|
+
"genders": "Native gender categories used to filter aggregate trends.",
|
|
125
|
+
"ageBuckets": "Native age groups used to filter aggregate trends.",
|
|
126
|
+
"includeKeywords": "Include related keyword details in the trend response.",
|
|
127
|
+
"includeDemographics": "Include available aggregate demographic breakdowns.",
|
|
128
|
+
"productCategories": "Native product category IDs or values used to filter trends.",
|
|
129
|
+
"verticals": "Native business verticals used to filter trends.",
|
|
130
|
+
"productLookbackDays": "Product-trend lookback window, in days.",
|
|
131
|
+
"productEngagementType": "Engagement signal used for product-trend analysis.",
|
|
132
|
+
"featuredInterest": "Featured interest category for the trends lookup.",
|
|
133
|
+
"reportType": "Native provider report type; determines supported metrics and dimensions.",
|
|
134
|
+
"promotionId": "Exact Pinterest product-group promotion ID.",
|
|
135
|
+
"type": "Provider object or operation type selected from this schema's allowed values.",
|
|
136
|
+
"productBreakdown": "Native product grouping used to join catalog items with reported insights.",
|
|
137
|
+
"catalogProductLimit": "Maximum catalog product records read for the join.",
|
|
138
|
+
"includeAdsets": "Include ad set configuration when checking brand safety.",
|
|
139
|
+
"includeBlockLists": "Include accessible brand-safety block-list metadata.",
|
|
140
|
+
"includeRawTargeting": "Include the native targeting specification for diagnostics.",
|
|
141
|
+
"cellEntityType": "Entity type represented by experiment cells.",
|
|
142
|
+
"includePagePosts": "Read related Facebook Page posts when the token has permission.",
|
|
143
|
+
"includeInstagramMedia": "Read related Instagram media when the token has permission.",
|
|
144
|
+
"includeInsights": "Include available insight metrics; additional provider permissions may be required.",
|
|
145
|
+
"edge": "Allowlisted Meta Graph edge to read on the selected node.",
|
|
146
|
+
"fields": "Native provider fields to return. Select only fields supported by the chosen object or report.",
|
|
147
|
+
"filtering": "Native provider filter expressions; credentials and account overrides are not allowed.",
|
|
148
|
+
"includeSummary": "Request the provider's available summary or total-count metadata.",
|
|
149
|
+
"actionBreakdowns": "Meta action-level breakdowns, subject to Insights compatibility rules.",
|
|
150
|
+
"actionAttributionWindows": "Meta attribution windows used to attribute action metrics.",
|
|
151
|
+
"timeIncrement": "Provider time-bucket size or supported aggregate value.",
|
|
152
|
+
"locationTypes": "Location dictionary categories to include in targeting search.",
|
|
153
|
+
"adFormat": "Native Meta ad preview format; must be compatible with this ad's creative.",
|
|
154
|
+
"handle": "Asynchronous catalog batch handle returned by the submission to inspect.",
|
|
155
|
+
"keepEmptyRows": "Keep provider rows whose selected metrics are all zero, where supported.",
|
|
156
|
+
"sampleLimit": "Maximum records included in the diagnostic sample.",
|
|
157
|
+
"isOpenFunnel": "Allow users to enter at any funnel step instead of requiring the first step.",
|
|
158
|
+
"visualizationType": "Supported funnel visualization mode.",
|
|
159
|
+
"breakdownLimit": "Maximum breakdown values to include in the funnel response.",
|
|
160
|
+
"nextActionLimit": "Maximum next actions returned for each funnel step.",
|
|
161
|
+
"fallbackToStepCounts": "If the advanced funnel is unavailable, return independent step counts with the limitation made explicit.",
|
|
162
|
+
"report": "Native GA4 report request object for this endpoint.",
|
|
163
|
+
"reports": "Native GA4 report request objects to execute in one batch.",
|
|
164
|
+
"dimensions": "Native reporting dimensions. Validate compatibility with the selected metrics.",
|
|
165
|
+
"compatibilityFilter": "Limit compatibility results to the requested GA4 compatibility category.",
|
|
166
|
+
"collection": "Allowlisted administrative resource collection to read.",
|
|
167
|
+
"settings": "Property configuration surfaces to inspect.",
|
|
168
|
+
"includeRecurring": "Include recurring audience-export configurations.",
|
|
169
|
+
"audienceExportName": "Full GA4 audience-export resource name returned by list_audience_exports.",
|
|
170
|
+
"queryLifetime": "Request lifetime reporting instead of the explicit reporting period where TikTok supports it.",
|
|
171
|
+
"declineThresholdPct": "Percentage decline used as a candidate signal for creative fatigue, requiring further evidence.",
|
|
172
|
+
"includeSavedAudiences": "Include accessible saved-audience metadata in the overlap analysis.",
|
|
173
|
+
"overlapThreshold": "Overlap threshold used to flag audience pairs, in the units declared by this schema.",
|
|
174
|
+
"serviceType": "TikTok report service category.",
|
|
175
|
+
"catalog": "Allowlisted targeting dictionary or catalog to inspect.",
|
|
176
|
+
"query": "Search text used to find matching entries in the selected catalogue.",
|
|
177
|
+
"parameters": "Native query parameters for this allowlisted read endpoint. Do not provide credentials or account overrides.",
|
|
178
|
+
"account_id": "Exact account ID selected for this platform in the GetMCPAds workspace.",
|
|
179
|
+
"platform": "Platform key identifying the connected advertising or analytics source.",
|
|
180
|
+
"id": "Exact saved object ID returned by the corresponding create or list tool.",
|
|
181
|
+
"revision": "Last known object revision, used to reject conflicting updates. Read the object before editing.",
|
|
182
|
+
"name": "Human-readable name for this object.",
|
|
183
|
+
"title": "Human-readable title for the resulting object or review.",
|
|
184
|
+
"clientLabel": "Display label identifying the client in the saved view.",
|
|
185
|
+
"config": "Complete saved-view configuration, including source, selected account, period and display options.",
|
|
186
|
+
"subject": "Entity or creative subject whose dated performance will be analyzed.",
|
|
187
|
+
"compare_previous": "Include the immediately preceding period of equal duration for comparison.",
|
|
188
|
+
"profile": "Creative brief profile containing the client's business context and analysis preferences.",
|
|
189
|
+
"primaryMetric": "Metric used to rank or evaluate the review; it must be available for the selected platform.",
|
|
190
|
+
"direction": "Whether higher or lower values of the primary metric represent improvement.",
|
|
191
|
+
"creative_ids": "Exact creative IDs to include in this review.",
|
|
192
|
+
"request_id": "Caller-generated request identifier. Reuse it for a retry of the same operation to avoid duplicate work.",
|
|
193
|
+
"expiresInDays": "Number of days before the public review sharing link expires.",
|
|
194
|
+
"client_id": "Exact saved business-client workspace ID.",
|
|
195
|
+
"review_id": "Exact review ID returned by the review creation or listing tool.",
|
|
196
|
+
"page_id": "Exact page identifier within the landing-page review.",
|
|
197
|
+
"entity_id": "Exact entity identifier in the selected review or platform context.",
|
|
198
|
+
"source_key": "Source identifier returned by the business review; keep the platform/account association intact.",
|
|
199
|
+
"metric_key": "Exact metric key in the review evidence.",
|
|
200
|
+
"follow_up_on": "Follow-up date for the decision, using the format declared in this schema.",
|
|
201
|
+
"hypothesis": "Specific hypothesis to record, supported by the review evidence.",
|
|
202
|
+
"successCriterion": "Measurable condition used to decide whether the recorded action succeeded.",
|
|
203
|
+
"outcome": "Observed outcome to record; distinguish evidence from an untested expectation.",
|
|
204
|
+
"status": "Status selected from the allowed values for this object or operation.",
|
|
205
|
+
"retry_source": "Source identifier to retry after reviewing its reported error.",
|
|
206
|
+
"site_origin": "Website origin to analyze, including https:// and hostname, without credentials or a path.",
|
|
207
|
+
"matchType": "Google Ads keyword match type for the forecast.",
|
|
208
|
+
"negativeKeywords": "Keyword phrases excluded from the keyword forecast.",
|
|
209
|
+
"negativeMatchType": "Match type applied to the negative keywords.",
|
|
210
|
+
"geoTargetIds": "Google Ads geo-target constant IDs for the intended locations.",
|
|
211
|
+
"languageId": "Google Ads language constant ID for the keyword request.",
|
|
212
|
+
"languageIds": "Google Ads language constant IDs for the intended audience.",
|
|
213
|
+
"network": "Google Ads search network selector used for planning.",
|
|
214
|
+
"biddingStrategy": "Supported bidding strategy for this temporary forecast. The request does not create a live campaign.",
|
|
215
|
+
"includeAdultKeywords": "Allow adult keyword ideas where supported by the provider and the selected market.",
|
|
216
|
+
"seedKeywords": "Seed keyword phrases from which Google generates keyword ideas.",
|
|
217
|
+
"includeAverageCpc": "Request average cost-per-click metrics when available.",
|
|
218
|
+
"includeDeviceBreakdown": "Include device-specific planning metrics when supported.",
|
|
219
|
+
"includeKeywordConcepts": "Include Google's keyword concept grouping metadata.",
|
|
220
|
+
"startYearMonth": "First year and month of the historical metrics period.",
|
|
221
|
+
"endYearMonth": "Last year and month of the historical metrics period.",
|
|
222
|
+
"includeDismissed": "Include recommendations already dismissed in the account.",
|
|
223
|
+
"operation": "Exact allowlisted operation to execute. Arbitrary provider operations are not accepted.",
|
|
224
|
+
"category": "Category selected from the provider dictionary or field catalogue.",
|
|
225
|
+
"selectable": "Filter the field catalogue by whether the field can appear in SELECT.",
|
|
226
|
+
"filterable": "Filter the field catalogue by whether the field can appear in WHERE.",
|
|
227
|
+
"sortable": "Filter the field catalogue by whether the field can appear in ORDER BY.",
|
|
228
|
+
"pageToken": "Opaque continuation token returned by the preceding provider response.",
|
|
229
|
+
"locationNames": "Human-readable location names for Google to resolve into geo-target constants.",
|
|
230
|
+
"locale": "Locale used for human-readable lookup results.",
|
|
231
|
+
"resource": "Native Google Ads resource from which the GAQL query selects rows.",
|
|
232
|
+
"cursor": "Opaque pagination cursor returned by the preceding response; omit for the first page.",
|
|
233
|
+
"kind": "Entity or request kind whose schema or data is being requested.",
|
|
234
|
+
"startTime": "Beginning of the reporting period as an ISO 8601 timestamp with an explicit timezone.",
|
|
235
|
+
"endTime": "End of the reporting period as an ISO 8601 timestamp with an explicit timezone, after startTime.",
|
|
236
|
+
"reportDimension": "Native dimension used to break down the Snapchat report."
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/tool-quality.ts
|
|
240
|
+
import { z } from "zod";
|
|
241
|
+
var RESULT_SCHEMA = {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
result: {
|
|
245
|
+
description: "Original tool result: parsed JSON when the text is JSON, otherwise the text or multiple MCP content blocks. Provider fields depend on the selected query.",
|
|
246
|
+
type: ["object", "array", "string", "number", "boolean", "null"]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required: ["result"],
|
|
250
|
+
additionalProperties: true
|
|
251
|
+
};
|
|
252
|
+
function toolAnnotations(write) {
|
|
253
|
+
return { readOnlyHint: !write, destructiveHint: write, idempotentHint: !write, openWorldHint: true };
|
|
254
|
+
}
|
|
255
|
+
function structuredResult(value) {
|
|
256
|
+
if (value.isError) return value;
|
|
257
|
+
let result = value.content;
|
|
258
|
+
const first = value.content[0];
|
|
259
|
+
if (value.content.length === 1 && first?.type === "text" && typeof first.text === "string") {
|
|
260
|
+
result = first.text;
|
|
261
|
+
try {
|
|
262
|
+
result = JSON.parse(first.text);
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { ...value, structuredContent: { ...value.structuredContent, result } };
|
|
267
|
+
}
|
|
268
|
+
var resultShape = { result: z.union([z.object({}).passthrough(), z.array(z.unknown()), z.string(), z.number(), z.boolean(), z.null()]).describe(RESULT_SCHEMA.properties.result.description) };
|
|
269
|
+
function installToolQuality(server) {
|
|
270
|
+
server.tool = ((name, description, inputSchema, handler) => {
|
|
271
|
+
const write = Object.hasOwn(inputSchema, "confirm");
|
|
272
|
+
inputSchema = Object.fromEntries(Object.entries(inputSchema).map(([key, schema]) => [key, schema.description || PARAMETER_DESCRIPTIONS[key] ? schema.describe(schema.description || PARAMETER_DESCRIPTIONS[key]) : schema]));
|
|
273
|
+
return server.registerTool(name, {
|
|
274
|
+
title: name.replace(/_/g, " "),
|
|
275
|
+
description,
|
|
276
|
+
inputSchema,
|
|
277
|
+
outputSchema: resultShape,
|
|
278
|
+
annotations: toolAnnotations(write)
|
|
279
|
+
}, async (...args) => structuredResult(await handler(...args)));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
1
283
|
// src/server.ts
|
|
2
284
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
285
|
|
|
4
286
|
// src/platforms/gsc/tools.ts
|
|
5
|
-
import { z } from "zod";
|
|
287
|
+
import { z as z2 } from "zod";
|
|
6
288
|
|
|
7
289
|
// src/core/errors.ts
|
|
8
290
|
var PlatformApiError = class extends Error {
|
|
@@ -17,13 +299,6 @@ var PlatformApiError = class extends Error {
|
|
|
17
299
|
this.retryAfter = retryAfter;
|
|
18
300
|
this.name = "PlatformApiError";
|
|
19
301
|
}
|
|
20
|
-
platform;
|
|
21
|
-
code;
|
|
22
|
-
isRateLimit;
|
|
23
|
-
isAuth;
|
|
24
|
-
isPermission;
|
|
25
|
-
suggestion;
|
|
26
|
-
retryAfter;
|
|
27
302
|
toMcpError() {
|
|
28
303
|
return {
|
|
29
304
|
error: this.message,
|
|
@@ -210,6 +485,33 @@ function flattenSearchAnalyticsResponse(response, dimensions) {
|
|
|
210
485
|
}
|
|
211
486
|
|
|
212
487
|
// src/platforms/gsc/gscClient.ts
|
|
488
|
+
function gscRouteClass(rawUrl) {
|
|
489
|
+
try {
|
|
490
|
+
const parsed = new URL(rawUrl);
|
|
491
|
+
if (parsed.pathname.endsWith("/searchAnalytics/query")) return "search_analytics";
|
|
492
|
+
if (parsed.pathname.endsWith("/urlInspection/index:inspect")) return "url_inspection";
|
|
493
|
+
if (parsed.pathname.includes("/sitemaps")) return "sitemaps";
|
|
494
|
+
if (parsed.pathname.includes("/sites")) return "sites";
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
return "gsc_api";
|
|
498
|
+
}
|
|
499
|
+
function codeOwnedApiFailure(status) {
|
|
500
|
+
if (status === 401 || status === 403) {
|
|
501
|
+
return { message: "GSC API authorization failed.", status: "AUTHORIZATION_FAILED" };
|
|
502
|
+
}
|
|
503
|
+
if (status === 429) {
|
|
504
|
+
return { message: "GSC API rate limit was reached.", status: "RATE_LIMITED" };
|
|
505
|
+
}
|
|
506
|
+
if (status >= 500) {
|
|
507
|
+
return { message: "GSC API is temporarily unavailable.", status: "UPSTREAM_UNAVAILABLE" };
|
|
508
|
+
}
|
|
509
|
+
return { message: "GSC API rejected the request.", status: "REQUEST_REJECTED" };
|
|
510
|
+
}
|
|
511
|
+
var GSC_AUTO_PAGINATION_MAX_PAGES = 4;
|
|
512
|
+
var GSC_AUTO_PAGINATION_MAX_ROWS = 1e5;
|
|
513
|
+
var GSC_AUTO_PAGINATION_DEADLINE_MS = 3e4;
|
|
514
|
+
var GSC_SINGLE_REQUEST_TIMEOUT_MS = 9e4;
|
|
213
515
|
function getAuthorizationUrl(clientId, redirectUri, state) {
|
|
214
516
|
const params = new URLSearchParams({
|
|
215
517
|
client_id: clientId,
|
|
@@ -283,16 +585,16 @@ var GSCClient = class {
|
|
|
283
585
|
// ============================================
|
|
284
586
|
// PRIVATE METHODS
|
|
285
587
|
// ============================================
|
|
286
|
-
async request(url, options = {}) {
|
|
588
|
+
async request(url, options = {}, timeoutMs = GSC_SINGLE_REQUEST_TIMEOUT_MS) {
|
|
589
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
590
|
+
throw new GSCApiException("GSC request deadline expired before dispatch", 408, "TIMEOUT");
|
|
591
|
+
}
|
|
287
592
|
const controller = new AbortController();
|
|
288
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
593
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
289
594
|
try {
|
|
290
595
|
const response = await fetch(url, {
|
|
291
596
|
...options,
|
|
292
597
|
signal: controller.signal,
|
|
293
|
-
// Forced after the spread: once a bearer token is attached, a redirect
|
|
294
|
-
// must never be followed, or the credential would be forwarded to
|
|
295
|
-
// whatever host the redirect names.
|
|
296
598
|
redirect: "error",
|
|
297
599
|
headers: {
|
|
298
600
|
Authorization: `Bearer ${this.accessToken}`,
|
|
@@ -301,21 +603,22 @@ var GSCClient = class {
|
|
|
301
603
|
}
|
|
302
604
|
});
|
|
303
605
|
if (!response.ok) {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
606
|
+
const failure = codeOwnedApiFailure(response.status);
|
|
607
|
+
console.error("[GSCClient] API error", {
|
|
608
|
+
status: response.status,
|
|
609
|
+
route: gscRouteClass(url)
|
|
610
|
+
});
|
|
308
611
|
throw new GSCApiException(
|
|
309
|
-
|
|
612
|
+
failure.message,
|
|
310
613
|
response.status,
|
|
311
|
-
|
|
614
|
+
failure.status
|
|
312
615
|
);
|
|
313
616
|
}
|
|
314
|
-
return response.json();
|
|
617
|
+
return await response.json();
|
|
315
618
|
} catch (error) {
|
|
316
619
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
317
620
|
throw new GSCApiException(
|
|
318
|
-
"GSC API request
|
|
621
|
+
"GSC API request exceeded its bounded deadline",
|
|
319
622
|
408,
|
|
320
623
|
"TIMEOUT"
|
|
321
624
|
);
|
|
@@ -365,39 +668,80 @@ var GSCClient = class {
|
|
|
365
668
|
const url = `${GSC_API_BASE}/webmasters/v3/sites/${encodedSiteUrl}/searchAnalytics/query`;
|
|
366
669
|
const MAX_ROWS_PER_REQUEST = 25e3;
|
|
367
670
|
const rowLimit = request.rowLimit ?? MAX_ROWS_PER_REQUEST;
|
|
368
|
-
const
|
|
671
|
+
const autoPaginate = request.rowLimit === void 0;
|
|
672
|
+
const paginationDeadline = Date.now() + GSC_AUTO_PAGINATION_DEADLINE_MS;
|
|
673
|
+
const remainingPaginationMs = () => {
|
|
674
|
+
const remaining = paginationDeadline - Date.now();
|
|
675
|
+
if (remaining < 1) {
|
|
676
|
+
throw new GSCApiException(
|
|
677
|
+
"GSC auto-pagination exceeded its 30-second total deadline",
|
|
678
|
+
408,
|
|
679
|
+
"PAGINATION_DEADLINE"
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return remaining;
|
|
683
|
+
};
|
|
369
684
|
const firstResponse = await this.request(url, {
|
|
370
685
|
method: "POST",
|
|
371
686
|
body: JSON.stringify({
|
|
372
687
|
...request,
|
|
373
688
|
rowLimit: Math.min(rowLimit, MAX_ROWS_PER_REQUEST),
|
|
374
|
-
startRow:
|
|
689
|
+
startRow: request.startRow ?? 0
|
|
375
690
|
})
|
|
376
|
-
});
|
|
691
|
+
}, autoPaginate ? remainingPaginationMs() : GSC_SINGLE_REQUEST_TIMEOUT_MS);
|
|
692
|
+
if (autoPaginate) remainingPaginationMs();
|
|
377
693
|
const returnedRows = firstResponse.rows?.length ?? 0;
|
|
378
694
|
if (request.rowLimit || returnedRows < MAX_ROWS_PER_REQUEST) {
|
|
379
|
-
return
|
|
695
|
+
return {
|
|
696
|
+
...firstResponse,
|
|
697
|
+
pagination: {
|
|
698
|
+
mode: autoPaginate ? "bounded_auto" : "single_page",
|
|
699
|
+
pageCount: 1,
|
|
700
|
+
rowCount: returnedRows,
|
|
701
|
+
maximumPages: autoPaginate ? GSC_AUTO_PAGINATION_MAX_PAGES : 1,
|
|
702
|
+
maximumRows: autoPaginate ? GSC_AUTO_PAGINATION_MAX_ROWS : rowLimit,
|
|
703
|
+
deadlineMs: autoPaginate ? GSC_AUTO_PAGINATION_DEADLINE_MS : GSC_SINGLE_REQUEST_TIMEOUT_MS,
|
|
704
|
+
truncated: false
|
|
705
|
+
}
|
|
706
|
+
};
|
|
380
707
|
}
|
|
381
708
|
const allRows = [...firstResponse.rows ?? []];
|
|
382
|
-
let currentOffset =
|
|
383
|
-
|
|
709
|
+
let currentOffset = (request.startRow ?? 0) + returnedRows;
|
|
710
|
+
let pageCount = 1;
|
|
711
|
+
let lastPageWasFull = returnedRows === MAX_ROWS_PER_REQUEST;
|
|
712
|
+
while (pageCount < GSC_AUTO_PAGINATION_MAX_PAGES && allRows.length < GSC_AUTO_PAGINATION_MAX_ROWS) {
|
|
713
|
+
const remainingRows = GSC_AUTO_PAGINATION_MAX_ROWS - allRows.length;
|
|
714
|
+
const requestedRows = Math.min(MAX_ROWS_PER_REQUEST, remainingRows);
|
|
384
715
|
const pageResponse = await this.request(url, {
|
|
385
716
|
method: "POST",
|
|
386
717
|
body: JSON.stringify({
|
|
387
718
|
...request,
|
|
388
|
-
rowLimit:
|
|
719
|
+
rowLimit: requestedRows,
|
|
389
720
|
startRow: currentOffset
|
|
390
721
|
})
|
|
391
|
-
});
|
|
722
|
+
}, remainingPaginationMs());
|
|
723
|
+
remainingPaginationMs();
|
|
392
724
|
const pageRows = pageResponse.rows?.length ?? 0;
|
|
725
|
+
pageCount += 1;
|
|
726
|
+
lastPageWasFull = pageRows === requestedRows;
|
|
393
727
|
if (pageRows === 0) break;
|
|
394
|
-
allRows.push(...pageResponse.rows ?? []);
|
|
728
|
+
allRows.push(...(pageResponse.rows ?? []).slice(0, remainingRows));
|
|
395
729
|
currentOffset += pageRows;
|
|
396
|
-
if (pageRows <
|
|
730
|
+
if (pageRows < requestedRows) break;
|
|
397
731
|
}
|
|
732
|
+
remainingPaginationMs();
|
|
398
733
|
return {
|
|
399
734
|
...firstResponse,
|
|
400
|
-
rows: allRows
|
|
735
|
+
rows: allRows.slice(0, GSC_AUTO_PAGINATION_MAX_ROWS),
|
|
736
|
+
pagination: {
|
|
737
|
+
mode: "bounded_auto",
|
|
738
|
+
pageCount,
|
|
739
|
+
rowCount: Math.min(allRows.length, GSC_AUTO_PAGINATION_MAX_ROWS),
|
|
740
|
+
maximumPages: GSC_AUTO_PAGINATION_MAX_PAGES,
|
|
741
|
+
maximumRows: GSC_AUTO_PAGINATION_MAX_ROWS,
|
|
742
|
+
deadlineMs: GSC_AUTO_PAGINATION_DEADLINE_MS,
|
|
743
|
+
truncated: lastPageWasFull && (pageCount >= GSC_AUTO_PAGINATION_MAX_PAGES || allRows.length >= GSC_AUTO_PAGINATION_MAX_ROWS)
|
|
744
|
+
}
|
|
401
745
|
};
|
|
402
746
|
}
|
|
403
747
|
// ============================================
|
|
@@ -838,36 +1182,37 @@ function planGSCQuery(params) {
|
|
|
838
1182
|
}
|
|
839
1183
|
|
|
840
1184
|
// src/platforms/gsc/tools.ts
|
|
841
|
-
var siteUrlSchema =
|
|
842
|
-
var datePresetSchema =
|
|
843
|
-
var searchTypeSchema =
|
|
844
|
-
var dataStateSchema =
|
|
845
|
-
var aggregationTypeSchema =
|
|
846
|
-
var
|
|
847
|
-
var
|
|
848
|
-
|
|
849
|
-
|
|
1185
|
+
var siteUrlSchema = z2.string().optional().describe("Search Console property URL, e.g. https://example.com/ or sc-domain:example.com. Uses GSC_SITE_URL when omitted.");
|
|
1186
|
+
var datePresetSchema = z2.enum(["today", "yesterday", "last7days", "last28days", "last3months", "last6months", "last12months", "last16months"]);
|
|
1187
|
+
var searchTypeSchema = z2.enum(["web", "image", "video", "news", "discover", "googleNews"]);
|
|
1188
|
+
var dataStateSchema = z2.enum(["final", "all", "hourly_all"]);
|
|
1189
|
+
var aggregationTypeSchema = z2.enum(["auto", "byPage", "byProperty", "byNewsShowcasePanel"]);
|
|
1190
|
+
var CompareSearchTypesSchema = z2.array(searchTypeSchema).min(1).max(6).refine((items) => new Set(items).size === items.length, "searchTypes must be unique");
|
|
1191
|
+
var limitSchema = z2.number().int().min(1).max(25e3).optional().default(1e3);
|
|
1192
|
+
var dateRangeSchema = z2.object({
|
|
1193
|
+
startDate: z2.string().describe("Start date YYYY-MM-DD"),
|
|
1194
|
+
endDate: z2.string().describe("End date YYYY-MM-DD")
|
|
850
1195
|
});
|
|
851
|
-
var comparisonDimensionSchema =
|
|
852
|
-
var sitemapBaselineSchema =
|
|
853
|
-
path:
|
|
854
|
-
submitted:
|
|
855
|
-
indexed:
|
|
856
|
-
warnings:
|
|
857
|
-
errors:
|
|
858
|
-
lastSubmitted:
|
|
859
|
-
lastDownloaded:
|
|
1196
|
+
var comparisonDimensionSchema = z2.enum(["query", "page"]);
|
|
1197
|
+
var sitemapBaselineSchema = z2.object({
|
|
1198
|
+
path: z2.string().describe("Sitemap URL/path returned by GSC."),
|
|
1199
|
+
submitted: z2.number().int().min(0).optional().default(0),
|
|
1200
|
+
indexed: z2.number().int().min(0).optional().default(0),
|
|
1201
|
+
warnings: z2.number().int().min(0).optional().default(0),
|
|
1202
|
+
errors: z2.number().int().min(0).optional().default(0),
|
|
1203
|
+
lastSubmitted: z2.string().optional(),
|
|
1204
|
+
lastDownloaded: z2.string().optional()
|
|
860
1205
|
});
|
|
861
|
-
var watchlistUrlSchema =
|
|
862
|
-
url:
|
|
863
|
-
label:
|
|
864
|
-
priority:
|
|
865
|
-
tags:
|
|
866
|
-
expectedVerdict:
|
|
867
|
-
expectedCanonical:
|
|
868
|
-
expectedCoverageState:
|
|
1206
|
+
var watchlistUrlSchema = z2.object({
|
|
1207
|
+
url: z2.string().url(),
|
|
1208
|
+
label: z2.string().optional(),
|
|
1209
|
+
priority: z2.enum(["low", "medium", "high", "critical"]).optional().default("medium"),
|
|
1210
|
+
tags: z2.array(z2.string()).optional().default([]),
|
|
1211
|
+
expectedVerdict: z2.enum(["PASS", "PARTIAL", "FAIL", "NEUTRAL", "VERDICT_UNSPECIFIED"]).optional().default("PASS"),
|
|
1212
|
+
expectedCanonical: z2.string().url().optional(),
|
|
1213
|
+
expectedCoverageState: z2.string().optional()
|
|
869
1214
|
});
|
|
870
|
-
var samplingObjectiveSchema =
|
|
1215
|
+
var samplingObjectiveSchema = z2.enum(["topTraffic", "lowCtr", "declining", "sitemapRisk", "staleSitemaps"]);
|
|
871
1216
|
var BULK_URL_INSPECTION_LIMIT = 10;
|
|
872
1217
|
var URL_INSPECTION_DAILY_QUOTA = 2e3;
|
|
873
1218
|
var DEFAULT_FRESHNESS_LAG_WARNING_DAYS = 3;
|
|
@@ -998,7 +1343,6 @@ var AuthenticatedGSCClient = class {
|
|
|
998
1343
|
this.tokenExpiresAt = Date.now() + 45 * 60 * 1e3;
|
|
999
1344
|
}
|
|
1000
1345
|
}
|
|
1001
|
-
config;
|
|
1002
1346
|
accessToken;
|
|
1003
1347
|
tokenExpiresAt = 0;
|
|
1004
1348
|
rateLimiter = new RateLimiter();
|
|
@@ -1528,26 +1872,26 @@ function registerGSCTools(server, config) {
|
|
|
1528
1872
|
Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for available fields and rules.`,
|
|
1529
1873
|
{
|
|
1530
1874
|
siteUrl: siteUrlSchema,
|
|
1531
|
-
metrics:
|
|
1532
|
-
dimensions:
|
|
1533
|
-
filters:
|
|
1534
|
-
field:
|
|
1535
|
-
operator:
|
|
1536
|
-
value:
|
|
1875
|
+
metrics: z2.array(z2.string()).optional().describe("Native: clicks, impressions, ctr, position. Calculated: ctrOpportunity, clicksPerImpression, impressionShare, clickShare."),
|
|
1876
|
+
dimensions: z2.array(z2.string()).max(5).optional().describe("Up to 5 dimensions selected from query, page, country, device, date, hour, searchAppearance."),
|
|
1877
|
+
filters: z2.array(z2.object({
|
|
1878
|
+
field: z2.string(),
|
|
1879
|
+
operator: z2.enum(["contains", "equals", "notContains", "notEquals", "includingRegex", "excludingRegex"]),
|
|
1880
|
+
value: z2.union([z2.string(), z2.number(), z2.boolean()])
|
|
1537
1881
|
})).optional(),
|
|
1538
1882
|
searchType: searchTypeSchema.optional().default("web"),
|
|
1539
1883
|
dataState: dataStateSchema.optional().default("all").describe("final=stable data, all=fresh daily data, hourly_all=fresh hourly data (use hour dimension)."),
|
|
1540
1884
|
aggregationType: aggregationTypeSchema.optional().default("auto").describe("Official GSC aggregation. byProperty is incompatible with page grouping/filtering and Discover/Google News. byNewsShowcasePanel requires discover/googleNews plus searchAppearance equals NEWS_SHOWCASE, and forbids page grouping/filtering or another searchAppearance filter."),
|
|
1541
1885
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
1542
|
-
dateRange:
|
|
1543
|
-
startDate:
|
|
1544
|
-
endDate:
|
|
1886
|
+
dateRange: z2.object({
|
|
1887
|
+
startDate: z2.string().describe("Start date YYYY-MM-DD"),
|
|
1888
|
+
endDate: z2.string().describe("End date YYYY-MM-DD")
|
|
1545
1889
|
}).optional(),
|
|
1546
|
-
orderBy:
|
|
1547
|
-
orderDirection:
|
|
1890
|
+
orderBy: z2.enum(["clicks", "impressions", "ctr", "position", "ctrOpportunity", "clicksPerImpression", "impressionShare", "clickShare"]).optional().default("clicks"),
|
|
1891
|
+
orderDirection: z2.enum(["ASC", "DESC"]).optional().default("DESC"),
|
|
1548
1892
|
limit: limitSchema,
|
|
1549
|
-
startRow:
|
|
1550
|
-
autoPaginate:
|
|
1893
|
+
startRow: z2.number().int().min(0).optional().describe("Optional 0-based row offset for explicit pagination."),
|
|
1894
|
+
autoPaginate: z2.boolean().optional().default(false).describe("When true, omit rowLimit so the GSC client auto-paginates until exhaustion.")
|
|
1551
1895
|
},
|
|
1552
1896
|
async ({ siteUrl, metrics, dimensions, filters, searchType, dataState, aggregationType, datePreset, dateRange, orderBy, orderDirection, limit, startRow, autoPaginate }) => {
|
|
1553
1897
|
try {
|
|
@@ -1597,6 +1941,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1597
1941
|
mode: autoPaginate ? "auto_startRow" : "single_page",
|
|
1598
1942
|
autoPaginate,
|
|
1599
1943
|
startRow: startRow ?? 0,
|
|
1944
|
+
pageCount: response.pagination?.pageCount ?? 1,
|
|
1945
|
+
maximumPages: response.pagination?.maximumPages ?? 1,
|
|
1946
|
+
maximumRows: response.pagination?.maximumRows ?? limit,
|
|
1947
|
+
deadlineMs: response.pagination?.deadlineMs ?? 9e4,
|
|
1948
|
+
truncated: response.pagination?.truncated ?? false,
|
|
1600
1949
|
limit: autoPaginate ? null : limit
|
|
1601
1950
|
},
|
|
1602
1951
|
dataState,
|
|
@@ -1613,8 +1962,8 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1613
1962
|
"Inspect a URL for Google indexing status, crawl info, mobile usability, AMP, and rich results. Quota: 2000 requests/day/property.",
|
|
1614
1963
|
{
|
|
1615
1964
|
siteUrl: siteUrlSchema,
|
|
1616
|
-
url:
|
|
1617
|
-
languageCode:
|
|
1965
|
+
url: z2.string().url().describe("Full URL to inspect."),
|
|
1966
|
+
languageCode: z2.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR.")
|
|
1618
1967
|
},
|
|
1619
1968
|
async ({ siteUrl, url, languageCode }) => {
|
|
1620
1969
|
try {
|
|
@@ -1639,9 +1988,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1639
1988
|
"Inspect multiple URLs with a conservative sequential limit and normalized URL Inspection results. Quota: 2000 requests/day/property.",
|
|
1640
1989
|
{
|
|
1641
1990
|
siteUrl: siteUrlSchema,
|
|
1642
|
-
urls:
|
|
1643
|
-
languageCode:
|
|
1644
|
-
continueOnError:
|
|
1991
|
+
urls: z2.array(z2.string().url()).min(1).max(BULK_URL_INSPECTION_LIMIT).describe(`Full URLs to inspect. Maximum ${BULK_URL_INSPECTION_LIMIT} per call to protect URL Inspection quota.`),
|
|
1992
|
+
languageCode: z2.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR."),
|
|
1993
|
+
continueOnError: z2.boolean().optional().default(true).describe("When true, returns per-URL errors instead of failing the entire batch.")
|
|
1645
1994
|
},
|
|
1646
1995
|
async ({ siteUrl, urls, languageCode, continueOnError }) => {
|
|
1647
1996
|
try {
|
|
@@ -1681,7 +2030,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1681
2030
|
"List sitemaps submitted for a Search Console property, including processing status and submitted/indexed counts.",
|
|
1682
2031
|
{
|
|
1683
2032
|
siteUrl: siteUrlSchema,
|
|
1684
|
-
sitemapIndex:
|
|
2033
|
+
sitemapIndex: z2.string().url().optional().describe("Optional sitemap index URL used to return only its child sitemaps.")
|
|
1685
2034
|
},
|
|
1686
2035
|
async ({ siteUrl, sitemapIndex }) => {
|
|
1687
2036
|
try {
|
|
@@ -1709,7 +2058,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1709
2058
|
"Get one submitted sitemap by full feed path, including type, fetch state, warnings/errors, and submitted/indexed content totals.",
|
|
1710
2059
|
{
|
|
1711
2060
|
siteUrl: siteUrlSchema,
|
|
1712
|
-
feedpath:
|
|
2061
|
+
feedpath: z2.string().url().describe("Full sitemap URL exactly as submitted to Search Console.")
|
|
1713
2062
|
},
|
|
1714
2063
|
async ({ siteUrl, feedpath }) => {
|
|
1715
2064
|
try {
|
|
@@ -1786,9 +2135,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1786
2135
|
"Compare performance across Search Console search types: web, image, video, news, discover, and Google News.",
|
|
1787
2136
|
{
|
|
1788
2137
|
siteUrl: siteUrlSchema,
|
|
1789
|
-
searchTypes:
|
|
2138
|
+
searchTypes: CompareSearchTypesSchema.optional().default(["web", "image", "video", "discover"]),
|
|
1790
2139
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
1791
|
-
dateRange:
|
|
2140
|
+
dateRange: z2.object({ startDate: z2.string(), endDate: z2.string() }).optional()
|
|
1792
2141
|
},
|
|
1793
2142
|
async ({ siteUrl, searchTypes, datePreset, dateRange }) => {
|
|
1794
2143
|
try {
|
|
@@ -1839,7 +2188,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1839
2188
|
{
|
|
1840
2189
|
siteUrl: siteUrlSchema,
|
|
1841
2190
|
searchType: searchTypeSchema.optional().default("web"),
|
|
1842
|
-
lookbackDays:
|
|
2191
|
+
lookbackDays: z2.number().int().min(3).max(30).optional().default(14).describe("Recent days to scan for daily data. Max 30.")
|
|
1843
2192
|
},
|
|
1844
2193
|
async ({ siteUrl, searchType, lookbackDays }) => {
|
|
1845
2194
|
try {
|
|
@@ -1897,11 +2246,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1897
2246
|
"Read-only monitoring snapshot for Search Analytics freshness, sitemap indexation totals, and an optional small URL Inspection sample.",
|
|
1898
2247
|
{
|
|
1899
2248
|
siteUrl: siteUrlSchema,
|
|
1900
|
-
searchTypes:
|
|
1901
|
-
lookbackDays:
|
|
1902
|
-
freshnessLagWarningDays:
|
|
1903
|
-
inspectUrls:
|
|
1904
|
-
languageCode:
|
|
2249
|
+
searchTypes: z2.array(searchTypeSchema).min(1).max(6).optional().default(["web"]),
|
|
2250
|
+
lookbackDays: z2.number().int().min(3).max(30).optional().default(14).describe("Recent days to scan for Search Analytics freshness."),
|
|
2251
|
+
freshnessLagWarningDays: z2.number().int().min(1).max(14).optional().default(DEFAULT_FRESHNESS_LAG_WARNING_DAYS).describe("Maximum acceptable lag in days before freshness is marked delayed/stale."),
|
|
2252
|
+
inspectUrls: z2.array(z2.string().url()).max(BULK_URL_INSPECTION_LIMIT).optional().default([]).describe(`Optional URLs to inspect. Maximum ${BULK_URL_INSPECTION_LIMIT} to protect URL Inspection quota.`),
|
|
2253
|
+
languageCode: z2.string().optional().describe("Optional IETF language tag for URL Inspection, e.g. en-US or fr-FR.")
|
|
1905
2254
|
},
|
|
1906
2255
|
async ({ siteUrl, searchTypes, lookbackDays, freshnessLagWarningDays, inspectUrls, languageCode }) => {
|
|
1907
2256
|
try {
|
|
@@ -2004,9 +2353,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2004
2353
|
"Read-only sitemap delta tracker. Fetches the current sitemap snapshot and compares it to an agent-provided baseline; no state is written.",
|
|
2005
2354
|
{
|
|
2006
2355
|
siteUrl: siteUrlSchema,
|
|
2007
|
-
baseline:
|
|
2008
|
-
includeUnchanged:
|
|
2009
|
-
minAbsIndexedDelta:
|
|
2356
|
+
baseline: z2.array(sitemapBaselineSchema).optional().default([]).describe("Previous snapshot entries from this tool's snapshot output."),
|
|
2357
|
+
includeUnchanged: z2.boolean().optional().default(false),
|
|
2358
|
+
minAbsIndexedDelta: z2.number().int().min(0).optional().default(1).describe("Minimum absolute indexed URL delta to include otherwise unchanged sitemaps.")
|
|
2010
2359
|
},
|
|
2011
2360
|
async ({ siteUrl, baseline, includeUnchanged, minAbsIndexedDelta }) => {
|
|
2012
2361
|
try {
|
|
@@ -2101,11 +2450,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2101
2450
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2102
2451
|
currentDateRange: dateRangeSchema.optional().describe("Current period. Defaults to datePreset."),
|
|
2103
2452
|
previousDateRange: dateRangeSchema.optional().describe("Previous period. Defaults to the immediately preceding period with the same length."),
|
|
2104
|
-
includePages:
|
|
2105
|
-
limit:
|
|
2106
|
-
topN:
|
|
2107
|
-
minImpressions:
|
|
2108
|
-
sortBy:
|
|
2453
|
+
includePages: z2.boolean().optional().default(false).describe("When true, groups by searchAppearance + page for page-level rich result trends."),
|
|
2454
|
+
limit: z2.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2455
|
+
topN: z2.number().int().min(1).max(100).optional().default(50),
|
|
2456
|
+
minImpressions: z2.number().int().min(0).optional().default(0),
|
|
2457
|
+
sortBy: z2.enum(["currentClicks", "currentImpressions", "clicksDelta", "impressionsDelta", "positionDelta"]).optional().default("impressionsDelta")
|
|
2109
2458
|
},
|
|
2110
2459
|
async ({ siteUrl, searchType, datePreset, currentDateRange, previousDateRange, includePages, limit, topN, minImpressions, sortBy }) => {
|
|
2111
2460
|
try {
|
|
@@ -2215,11 +2564,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2215
2564
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2216
2565
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2217
2566
|
dateRange: dateRangeSchema.optional(),
|
|
2218
|
-
objectives:
|
|
2219
|
-
pageLimit:
|
|
2220
|
-
maxInspectionUrls:
|
|
2221
|
-
minImpressionsForLowCtr:
|
|
2222
|
-
staleSitemapDays:
|
|
2567
|
+
objectives: z2.array(samplingObjectiveSchema).min(1).max(5).optional().default(["topTraffic", "lowCtr", "declining", "sitemapRisk", "staleSitemaps"]),
|
|
2568
|
+
pageLimit: z2.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2569
|
+
maxInspectionUrls: z2.number().int().min(1).max(BULK_URL_INSPECTION_LIMIT).optional().default(BULK_URL_INSPECTION_LIMIT),
|
|
2570
|
+
minImpressionsForLowCtr: z2.number().int().min(1).optional().default(100),
|
|
2571
|
+
staleSitemapDays: z2.number().int().min(1).max(180).optional().default(14)
|
|
2223
2572
|
},
|
|
2224
2573
|
async ({ siteUrl, searchType, datePreset, dateRange, objectives, pageLimit, maxInspectionUrls, minImpressionsForLowCtr, staleSitemapDays }) => {
|
|
2225
2574
|
try {
|
|
@@ -2348,9 +2697,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2348
2697
|
"Read-only URL Inspection watchlist for critical URLs. Inspects up to 10 URLs and returns alert/review/ok assessments without storing state.",
|
|
2349
2698
|
{
|
|
2350
2699
|
siteUrl: siteUrlSchema,
|
|
2351
|
-
urls:
|
|
2352
|
-
languageCode:
|
|
2353
|
-
continueOnError:
|
|
2700
|
+
urls: z2.array(watchlistUrlSchema).min(1).max(BULK_URL_INSPECTION_LIMIT).describe(`Watchlist entries. Maximum ${BULK_URL_INSPECTION_LIMIT} per call to protect URL Inspection quota.`),
|
|
2701
|
+
languageCode: z2.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR."),
|
|
2702
|
+
continueOnError: z2.boolean().optional().default(true)
|
|
2354
2703
|
},
|
|
2355
2704
|
async ({ siteUrl, urls, languageCode, continueOnError }) => {
|
|
2356
2705
|
try {
|
|
@@ -2417,10 +2766,10 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2417
2766
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2418
2767
|
currentDateRange: dateRangeSchema.optional().describe("Current period. Defaults to datePreset."),
|
|
2419
2768
|
previousDateRange: dateRangeSchema.optional().describe("Previous period. Defaults to the immediately preceding period with the same length."),
|
|
2420
|
-
limit:
|
|
2421
|
-
topN:
|
|
2422
|
-
minAbsClickDelta:
|
|
2423
|
-
sortBy:
|
|
2769
|
+
limit: z2.number().int().min(1).max(25e3).optional().default(5e3).describe("Rows to fetch for each period."),
|
|
2770
|
+
topN: z2.number().int().min(1).max(100).optional().default(25),
|
|
2771
|
+
minAbsClickDelta: z2.number().min(0).optional().default(0).describe("Filter out rows with smaller absolute click delta."),
|
|
2772
|
+
sortBy: z2.enum(["clicksDelta", "impressionsDelta", "ctrDelta", "positionDelta"]).optional().default("clicksDelta")
|
|
2424
2773
|
},
|
|
2425
2774
|
async ({ siteUrl, dimension, searchType, datePreset, currentDateRange, previousDateRange, limit, topN, minAbsClickDelta, sortBy }) => {
|
|
2426
2775
|
try {
|
|
@@ -2536,11 +2885,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2536
2885
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2537
2886
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2538
2887
|
dateRange: dateRangeSchema.optional(),
|
|
2539
|
-
brandTerms:
|
|
2540
|
-
categoryRules:
|
|
2541
|
-
limit:
|
|
2542
|
-
maxClusters:
|
|
2543
|
-
topQueriesPerCluster:
|
|
2888
|
+
brandTerms: z2.array(z2.string()).optional().default([]).describe("Optional brand terms. Defaults to terms derived from the property domain."),
|
|
2889
|
+
categoryRules: z2.record(z2.array(z2.string())).optional().describe("Optional map of category name to matching tokens/phrases."),
|
|
2890
|
+
limit: z2.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2891
|
+
maxClusters: z2.number().int().min(1).max(100).optional().default(25),
|
|
2892
|
+
topQueriesPerCluster: z2.number().int().min(1).max(25).optional().default(10)
|
|
2544
2893
|
},
|
|
2545
2894
|
async ({ siteUrl, searchType, datePreset, dateRange, brandTerms, categoryRules, limit, maxClusters, topQueriesPerCluster }) => {
|
|
2546
2895
|
try {
|
|
@@ -2617,11 +2966,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2617
2966
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2618
2967
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2619
2968
|
dateRange: dateRangeSchema.optional(),
|
|
2620
|
-
limit:
|
|
2621
|
-
minPages:
|
|
2622
|
-
minImpressions:
|
|
2623
|
-
maxTopPageClickShare:
|
|
2624
|
-
topN:
|
|
2969
|
+
limit: z2.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2970
|
+
minPages: z2.number().int().min(2).max(10).optional().default(2),
|
|
2971
|
+
minImpressions: z2.number().int().min(0).optional().default(100),
|
|
2972
|
+
maxTopPageClickShare: z2.number().min(0.1).max(0.99).optional().default(0.8).describe("Only flag queries where the top page owns less than or equal to this click share."),
|
|
2973
|
+
topN: z2.number().int().min(1).max(100).optional().default(25)
|
|
2625
2974
|
},
|
|
2626
2975
|
async ({ siteUrl, searchType, datePreset, dateRange, limit, minPages, minImpressions, maxTopPageClickShare, topN }) => {
|
|
2627
2976
|
try {
|
|
@@ -2717,9 +3066,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2717
3066
|
"gsc_validate_query",
|
|
2718
3067
|
"Validate a Search Console metric/dimension/search type combination before executing it.",
|
|
2719
3068
|
{
|
|
2720
|
-
dimensions:
|
|
3069
|
+
dimensions: z2.array(z2.string()).optional().describe("Dimensions to validate."),
|
|
2721
3070
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2722
|
-
dateRange:
|
|
3071
|
+
dateRange: z2.object({ startDate: z2.string(), endDate: z2.string() }).optional()
|
|
2723
3072
|
},
|
|
2724
3073
|
async ({ dimensions, searchType, dateRange }) => {
|
|
2725
3074
|
try {
|
|
@@ -3002,26 +3351,27 @@ function registerGSC(server, config) {
|
|
|
3002
3351
|
}
|
|
3003
3352
|
|
|
3004
3353
|
// src/server.ts
|
|
3005
|
-
var PACKAGE_VERSION = "
|
|
3354
|
+
var PACKAGE_VERSION = "2.0.0";
|
|
3006
3355
|
function createServer(config) {
|
|
3007
3356
|
const server = new McpServer(
|
|
3008
|
-
{ name: "google-search-console-mcp", version: PACKAGE_VERSION },
|
|
3357
|
+
{ name: "google-search-console-mcp", version: PACKAGE_VERSION, title: "Google Search Console", websiteUrl: "https://www.getmcpads.com/tools/search-console", icons: [{ src: "https://mcp.getmcpads.com/icon.svg", mimeType: "image/svg+xml" }] },
|
|
3009
3358
|
{ capabilities: { tools: { listChanged: true }, resources: { subscribe: false, listChanged: true } } }
|
|
3010
3359
|
);
|
|
3360
|
+
installToolQuality(server);
|
|
3011
3361
|
registerGSC(server, config);
|
|
3012
3362
|
logger.system(`google-search-console-mcp v${PACKAGE_VERSION} ready, read-only`);
|
|
3013
3363
|
return server;
|
|
3014
3364
|
}
|
|
3015
3365
|
|
|
3016
3366
|
// src/config.ts
|
|
3017
|
-
import { z as
|
|
3018
|
-
var configSchema =
|
|
3019
|
-
clientId:
|
|
3020
|
-
clientSecret:
|
|
3021
|
-
refreshToken:
|
|
3022
|
-
accessToken:
|
|
3023
|
-
defaultSiteUrl:
|
|
3024
|
-
logLevel:
|
|
3367
|
+
import { z as z3 } from "zod";
|
|
3368
|
+
var configSchema = z3.object({
|
|
3369
|
+
clientId: z3.string().optional(),
|
|
3370
|
+
clientSecret: z3.string().optional(),
|
|
3371
|
+
refreshToken: z3.string().optional(),
|
|
3372
|
+
accessToken: z3.string().optional(),
|
|
3373
|
+
defaultSiteUrl: z3.string().optional(),
|
|
3374
|
+
logLevel: z3.enum(["debug", "info", "warn", "error"]).default("info")
|
|
3025
3375
|
});
|
|
3026
3376
|
function loadConfig() {
|
|
3027
3377
|
const raw = {
|