@getmcpads/google-search-console-mcp-server 1.0.2 → 2.0.1
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 +27 -0
- package/README.md +99 -62
- 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 +13 -8
- package/server-card.json +2191 -0
- package/server.json +55 -0
package/dist/cli.js
CHANGED
|
@@ -64,11 +64,293 @@ function loadConfig() {
|
|
|
64
64
|
return config;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// src/parameter-descriptions.ts
|
|
68
|
+
var PARAMETER_DESCRIPTIONS = {
|
|
69
|
+
"filters": "Filters combined according to this tool's query schema. Use provider field names and the declared operators.",
|
|
70
|
+
"searchType": "Search surface to report, such as web, image, video, news or Discover. Availability depends on the property.",
|
|
71
|
+
"searchTypes": "Search surfaces to compare. Each surface is queried separately; do not sum overlapping surfaces.",
|
|
72
|
+
"datePreset": "Named reporting period. Use the custom date range when you need exact start and end dates.",
|
|
73
|
+
"dateRange": "Explicit reporting start and end dates in YYYY-MM-DD format.",
|
|
74
|
+
"timeRange": "Explicit provider reporting period with since and until dates.",
|
|
75
|
+
"startDate": "First date of the reporting period, in YYYY-MM-DD format.",
|
|
76
|
+
"endDate": "Last date of the reporting period, in YYYY-MM-DD format, on or after startDate.",
|
|
77
|
+
"since": "Beginning of the requested reporting period; use the format accepted by the provider.",
|
|
78
|
+
"until": "End of the requested reporting period; use the format accepted by the provider.",
|
|
79
|
+
"orderBy": "Field used to order report rows.",
|
|
80
|
+
"orderField": "Provider field used to order report rows.",
|
|
81
|
+
"orderDirection": "Sort direction for the selected ordering field.",
|
|
82
|
+
"orderType": "Sort direction for the selected ordering field.",
|
|
83
|
+
"sortBy": "Metric or dimension used to rank the returned results.",
|
|
84
|
+
"sort": "Native provider ordering expressions.",
|
|
85
|
+
"limit": "Maximum number of returned items or rows. The declared bounds and default apply; use pagination for additional results.",
|
|
86
|
+
"topN": "Maximum number of highest-ranked results to return.",
|
|
87
|
+
"pageSize": "Maximum items requested per page. Follow the returned pagination information for remaining results.",
|
|
88
|
+
"page": "Page number to request, starting at 1.",
|
|
89
|
+
"offset": "Zero-based number of rows to skip before returning results.",
|
|
90
|
+
"bookmark": "Opaque Pinterest pagination bookmark returned by the previous response. Omit on the first request.",
|
|
91
|
+
"after": "Opaque cursor for the next page returned by Meta.",
|
|
92
|
+
"before": "Opaque cursor for the previous page returned by Meta.",
|
|
93
|
+
"includeUnchanged": "Include entries with no observed change in the comparison.",
|
|
94
|
+
"minImpressions": "Minimum reported impressions required for a row to enter the analysis.",
|
|
95
|
+
"objectives": "Analysis goals that determine the recommended large-site sampling strategy.",
|
|
96
|
+
"pageLimit": "Maximum number of pages considered for the sampling plan.",
|
|
97
|
+
"maxInspectionUrls": "Maximum URL Inspection candidates included in the plan; inspection calls consume provider quota.",
|
|
98
|
+
"minImpressionsForLowCtr": "Minimum impressions before a page is considered for low-CTR analysis.",
|
|
99
|
+
"staleSitemapDays": "Number of days after which a sitemap's last download is considered stale.",
|
|
100
|
+
"continueOnError": "Continue with other URLs when one inspection fails, returning individual failures.",
|
|
101
|
+
"dimension": "Reporting dimension used to group and compare results.",
|
|
102
|
+
"maxClusters": "Maximum query clusters to return.",
|
|
103
|
+
"topQueriesPerCluster": "Maximum example queries returned for each cluster.",
|
|
104
|
+
"minPages": "Minimum distinct pages ranking for a query before it is considered a cannibalization candidate.",
|
|
105
|
+
"adAccountId": "Owning advertising account ID. Use the exact ID returned by account discovery; do not substitute a campaign or business ID.",
|
|
106
|
+
"advertiserId": "TikTok advertiser account ID as a string.",
|
|
107
|
+
"customerId": "Google Ads customer ID, without hyphens.",
|
|
108
|
+
"propertyId": "Google Analytics property ID; use the property, not the account or measurement ID.",
|
|
109
|
+
"level": "Entity or aggregation level for the requested report.",
|
|
110
|
+
"dataLevel": "TikTok reporting aggregation level. Metrics and dimensions must be compatible with this level.",
|
|
111
|
+
"granularity": "Time bucket used to aggregate reporting rows.",
|
|
112
|
+
"columns": "Native Pinterest reporting column names. Discover supported columns before selecting metrics.",
|
|
113
|
+
"executionMode": "Choose the supported synchronous, asynchronous or automatic report execution strategy.",
|
|
114
|
+
"entityIds": "Exact IDs of the entities to include in the selected reporting level.",
|
|
115
|
+
"targetingTypes": "Native targeting categories used to break down delivery.",
|
|
116
|
+
"clickWindowDays": "Click-through attribution window, in days.",
|
|
117
|
+
"engagementWindowDays": "Engagement-through attribution window, in days.",
|
|
118
|
+
"viewWindowDays": "View-through attribution window, in days.",
|
|
119
|
+
"conversionReportTime": "Whether conversions are assigned to the ad action date or the conversion date.",
|
|
120
|
+
"attributionTypes": "Provider attribution categories included in the report.",
|
|
121
|
+
"reportingTimezone": "Provider reporting timezone selector. Keep it consistent when comparing periods.",
|
|
122
|
+
"campaignIds": "Restrict results to these campaign IDs.",
|
|
123
|
+
"adGroupIds": "Restrict results to these ad group IDs.",
|
|
124
|
+
"adgroupIds": "Restrict TikTok results to these ad group IDs.",
|
|
125
|
+
"adIds": "Restrict results to these ad IDs.",
|
|
126
|
+
"campaignId": "Exact campaign ID in the selected advertising account.",
|
|
127
|
+
"adGroupId": "Exact ad group ID in the selected advertising account.",
|
|
128
|
+
"adSetId": "Exact Meta ad set ID in the selected advertising account.",
|
|
129
|
+
"adId": "Exact ad ID in the selected advertising account.",
|
|
130
|
+
"entityId": "Exact ID of the entity selected by this operation. It must belong to the selected account.",
|
|
131
|
+
"onlyWithPeriodDelivery": "Keep only creatives with observed delivery in the requested date range.",
|
|
132
|
+
"breakdown": "Native reporting breakdown used to group results.",
|
|
133
|
+
"breakdowns": "Native reporting breakdowns. Check compatibility before combining them.",
|
|
134
|
+
"productGroupIds": "Restrict reporting to these product group IDs.",
|
|
135
|
+
"productItemIds": "Restrict reporting to these product item IDs.",
|
|
136
|
+
"includeProductSamples": "Include a limited sample of product records alongside inventory diagnostics.",
|
|
137
|
+
"sampleProductGroups": "Maximum product groups to sample for inventory diagnostics.",
|
|
138
|
+
"reportName": "Provider report name identifying the requested report recipe.",
|
|
139
|
+
"conversionProductAttributionType": "Pinterest product-conversion attribution category.",
|
|
140
|
+
"conversionProductBreakdown": "Pinterest product-conversion grouping level.",
|
|
141
|
+
"productSkuIds": "Restrict product-conversion reporting to these SKU IDs.",
|
|
142
|
+
"request": "Exact request schema name to inspect before composing a write payload.",
|
|
143
|
+
"entity": "Entity collection or level to inspect.",
|
|
144
|
+
"entityType": "Provider entity type to retrieve.",
|
|
145
|
+
"entityStatuses": "Native status values used to filter the entity collection.",
|
|
146
|
+
"statusFilter": "Native status values used to filter the returned entities.",
|
|
147
|
+
"targetingType": "Targeting dictionary to inspect, such as interests, locations or demographics.",
|
|
148
|
+
"interestId": "Native interest ID for the targeting lookup.",
|
|
149
|
+
"mode": "Operation or analysis mode. Only the values declared in this schema are supported.",
|
|
150
|
+
"matchTypes": "Keyword match types included in the lookup.",
|
|
151
|
+
"countryCode": "Country code used to localize keyword or targeting results.",
|
|
152
|
+
"keywords": "Keyword phrases to analyze; use the language and market of the intended audience.",
|
|
153
|
+
"term": "Search term used for the keyword lookup.",
|
|
154
|
+
"terms": "Search terms used for the keyword lookup.",
|
|
155
|
+
"audienceId": "Exact ID of the audience to inspect or modify.",
|
|
156
|
+
"customerListId": "Exact customer-list ID. Reading metadata does not upload customer data.",
|
|
157
|
+
"businessId": "Exact business account ID used for the lookup.",
|
|
158
|
+
"accountType": "Provider account category used to filter results.",
|
|
159
|
+
"ownershipType": "Filter audiences by provider ownership category.",
|
|
160
|
+
"excludeNca": "Whether to exclude Pinterest new-customer-acquisition audiences from this lookup.",
|
|
161
|
+
"audienceInsightType": "Pinterest audience-insights category to request.",
|
|
162
|
+
"surfaces": "Conversion-setup surfaces to inspect, according to the supported values.",
|
|
163
|
+
"includeDeletedTags": "Include deleted conversion tags in diagnostics.",
|
|
164
|
+
"lookbackPeriod": "Provider lookback period used for conversion diagnostics.",
|
|
165
|
+
"sourcePlatform": "Filter conversion data by source platform.",
|
|
166
|
+
"ingestionSource": "Filter conversion events by ingestion source.",
|
|
167
|
+
"catalogId": "Exact product catalog ID accessible to the selected account.",
|
|
168
|
+
"feedId": "Exact catalog feed ID.",
|
|
169
|
+
"processingResultId": "Exact catalog feed processing result ID to inspect.",
|
|
170
|
+
"productGroupId": "Exact product group ID.",
|
|
171
|
+
"productSetId": "Exact Meta product set ID belonging to the selected catalog.",
|
|
172
|
+
"exportType": "Provider export category to request.",
|
|
173
|
+
"action": "Supported operation within this tool's scoped provider surface.",
|
|
174
|
+
"includeDetails": "Include detailed provider records in addition to summary information.",
|
|
175
|
+
"leadFormId": "Exact lead form ID to inspect.",
|
|
176
|
+
"subscriptionId": "Exact subscription ID to inspect.",
|
|
177
|
+
"assetId": "Exact provider asset ID.",
|
|
178
|
+
"memberId": "Exact business member ID used for the read lookup.",
|
|
179
|
+
"partnerId": "Exact business partner ID used for the read lookup.",
|
|
180
|
+
"pinId": "Exact Pinterest pin ID.",
|
|
181
|
+
"pinIds": "Exact Pinterest pin IDs to include.",
|
|
182
|
+
"metrics": "Native or documented calculated metric names to request. Check compatibility with the selected dimensions.",
|
|
183
|
+
"metric": "Metric used for the analysis; use a name from the platform metric catalogue.",
|
|
184
|
+
"boardId": "Exact Pinterest board ID.",
|
|
185
|
+
"searchTerm": "Text used to filter or search the selected collection.",
|
|
186
|
+
"region": "Provider region selector used to localize trends.",
|
|
187
|
+
"productRegion": "Region used for product trend analysis.",
|
|
188
|
+
"trendType": "Provider trend category to return.",
|
|
189
|
+
"interests": "Native interest categories used to filter trends.",
|
|
190
|
+
"genders": "Native gender categories used to filter aggregate trends.",
|
|
191
|
+
"ageBuckets": "Native age groups used to filter aggregate trends.",
|
|
192
|
+
"includeKeywords": "Include related keyword details in the trend response.",
|
|
193
|
+
"includeDemographics": "Include available aggregate demographic breakdowns.",
|
|
194
|
+
"productCategories": "Native product category IDs or values used to filter trends.",
|
|
195
|
+
"verticals": "Native business verticals used to filter trends.",
|
|
196
|
+
"productLookbackDays": "Product-trend lookback window, in days.",
|
|
197
|
+
"productEngagementType": "Engagement signal used for product-trend analysis.",
|
|
198
|
+
"featuredInterest": "Featured interest category for the trends lookup.",
|
|
199
|
+
"reportType": "Native provider report type; determines supported metrics and dimensions.",
|
|
200
|
+
"promotionId": "Exact Pinterest product-group promotion ID.",
|
|
201
|
+
"type": "Provider object or operation type selected from this schema's allowed values.",
|
|
202
|
+
"productBreakdown": "Native product grouping used to join catalog items with reported insights.",
|
|
203
|
+
"catalogProductLimit": "Maximum catalog product records read for the join.",
|
|
204
|
+
"includeAdsets": "Include ad set configuration when checking brand safety.",
|
|
205
|
+
"includeBlockLists": "Include accessible brand-safety block-list metadata.",
|
|
206
|
+
"includeRawTargeting": "Include the native targeting specification for diagnostics.",
|
|
207
|
+
"cellEntityType": "Entity type represented by experiment cells.",
|
|
208
|
+
"includePagePosts": "Read related Facebook Page posts when the token has permission.",
|
|
209
|
+
"includeInstagramMedia": "Read related Instagram media when the token has permission.",
|
|
210
|
+
"includeInsights": "Include available insight metrics; additional provider permissions may be required.",
|
|
211
|
+
"edge": "Allowlisted Meta Graph edge to read on the selected node.",
|
|
212
|
+
"fields": "Native provider fields to return. Select only fields supported by the chosen object or report.",
|
|
213
|
+
"filtering": "Native provider filter expressions; credentials and account overrides are not allowed.",
|
|
214
|
+
"includeSummary": "Request the provider's available summary or total-count metadata.",
|
|
215
|
+
"actionBreakdowns": "Meta action-level breakdowns, subject to Insights compatibility rules.",
|
|
216
|
+
"actionAttributionWindows": "Meta attribution windows used to attribute action metrics.",
|
|
217
|
+
"timeIncrement": "Provider time-bucket size or supported aggregate value.",
|
|
218
|
+
"locationTypes": "Location dictionary categories to include in targeting search.",
|
|
219
|
+
"adFormat": "Native Meta ad preview format; must be compatible with this ad's creative.",
|
|
220
|
+
"handle": "Asynchronous catalog batch handle returned by the submission to inspect.",
|
|
221
|
+
"keepEmptyRows": "Keep provider rows whose selected metrics are all zero, where supported.",
|
|
222
|
+
"sampleLimit": "Maximum records included in the diagnostic sample.",
|
|
223
|
+
"isOpenFunnel": "Allow users to enter at any funnel step instead of requiring the first step.",
|
|
224
|
+
"visualizationType": "Supported funnel visualization mode.",
|
|
225
|
+
"breakdownLimit": "Maximum breakdown values to include in the funnel response.",
|
|
226
|
+
"nextActionLimit": "Maximum next actions returned for each funnel step.",
|
|
227
|
+
"fallbackToStepCounts": "If the advanced funnel is unavailable, return independent step counts with the limitation made explicit.",
|
|
228
|
+
"report": "Native GA4 report request object for this endpoint.",
|
|
229
|
+
"reports": "Native GA4 report request objects to execute in one batch.",
|
|
230
|
+
"dimensions": "Native reporting dimensions. Validate compatibility with the selected metrics.",
|
|
231
|
+
"compatibilityFilter": "Limit compatibility results to the requested GA4 compatibility category.",
|
|
232
|
+
"collection": "Allowlisted administrative resource collection to read.",
|
|
233
|
+
"settings": "Property configuration surfaces to inspect.",
|
|
234
|
+
"includeRecurring": "Include recurring audience-export configurations.",
|
|
235
|
+
"audienceExportName": "Full GA4 audience-export resource name returned by list_audience_exports.",
|
|
236
|
+
"queryLifetime": "Request lifetime reporting instead of the explicit reporting period where TikTok supports it.",
|
|
237
|
+
"declineThresholdPct": "Percentage decline used as a candidate signal for creative fatigue, requiring further evidence.",
|
|
238
|
+
"includeSavedAudiences": "Include accessible saved-audience metadata in the overlap analysis.",
|
|
239
|
+
"overlapThreshold": "Overlap threshold used to flag audience pairs, in the units declared by this schema.",
|
|
240
|
+
"serviceType": "TikTok report service category.",
|
|
241
|
+
"catalog": "Allowlisted targeting dictionary or catalog to inspect.",
|
|
242
|
+
"query": "Search text used to find matching entries in the selected catalogue.",
|
|
243
|
+
"parameters": "Native query parameters for this allowlisted read endpoint. Do not provide credentials or account overrides.",
|
|
244
|
+
"account_id": "Exact account ID selected for this platform in the GetMCPAds workspace.",
|
|
245
|
+
"platform": "Platform key identifying the connected advertising or analytics source.",
|
|
246
|
+
"id": "Exact saved object ID returned by the corresponding create or list tool.",
|
|
247
|
+
"revision": "Last known object revision, used to reject conflicting updates. Read the object before editing.",
|
|
248
|
+
"name": "Human-readable name for this object.",
|
|
249
|
+
"title": "Human-readable title for the resulting object or review.",
|
|
250
|
+
"clientLabel": "Display label identifying the client in the saved view.",
|
|
251
|
+
"config": "Complete saved-view configuration, including source, selected account, period and display options.",
|
|
252
|
+
"subject": "Entity or creative subject whose dated performance will be analyzed.",
|
|
253
|
+
"compare_previous": "Include the immediately preceding period of equal duration for comparison.",
|
|
254
|
+
"profile": "Creative brief profile containing the client's business context and analysis preferences.",
|
|
255
|
+
"primaryMetric": "Metric used to rank or evaluate the review; it must be available for the selected platform.",
|
|
256
|
+
"direction": "Whether higher or lower values of the primary metric represent improvement.",
|
|
257
|
+
"creative_ids": "Exact creative IDs to include in this review.",
|
|
258
|
+
"request_id": "Caller-generated request identifier. Reuse it for a retry of the same operation to avoid duplicate work.",
|
|
259
|
+
"expiresInDays": "Number of days before the public review sharing link expires.",
|
|
260
|
+
"client_id": "Exact saved business-client workspace ID.",
|
|
261
|
+
"review_id": "Exact review ID returned by the review creation or listing tool.",
|
|
262
|
+
"page_id": "Exact page identifier within the landing-page review.",
|
|
263
|
+
"entity_id": "Exact entity identifier in the selected review or platform context.",
|
|
264
|
+
"source_key": "Source identifier returned by the business review; keep the platform/account association intact.",
|
|
265
|
+
"metric_key": "Exact metric key in the review evidence.",
|
|
266
|
+
"follow_up_on": "Follow-up date for the decision, using the format declared in this schema.",
|
|
267
|
+
"hypothesis": "Specific hypothesis to record, supported by the review evidence.",
|
|
268
|
+
"successCriterion": "Measurable condition used to decide whether the recorded action succeeded.",
|
|
269
|
+
"outcome": "Observed outcome to record; distinguish evidence from an untested expectation.",
|
|
270
|
+
"status": "Status selected from the allowed values for this object or operation.",
|
|
271
|
+
"retry_source": "Source identifier to retry after reviewing its reported error.",
|
|
272
|
+
"site_origin": "Website origin to analyze, including https:// and hostname, without credentials or a path.",
|
|
273
|
+
"matchType": "Google Ads keyword match type for the forecast.",
|
|
274
|
+
"negativeKeywords": "Keyword phrases excluded from the keyword forecast.",
|
|
275
|
+
"negativeMatchType": "Match type applied to the negative keywords.",
|
|
276
|
+
"geoTargetIds": "Google Ads geo-target constant IDs for the intended locations.",
|
|
277
|
+
"languageId": "Google Ads language constant ID for the keyword request.",
|
|
278
|
+
"languageIds": "Google Ads language constant IDs for the intended audience.",
|
|
279
|
+
"network": "Google Ads search network selector used for planning.",
|
|
280
|
+
"biddingStrategy": "Supported bidding strategy for this temporary forecast. The request does not create a live campaign.",
|
|
281
|
+
"includeAdultKeywords": "Allow adult keyword ideas where supported by the provider and the selected market.",
|
|
282
|
+
"seedKeywords": "Seed keyword phrases from which Google generates keyword ideas.",
|
|
283
|
+
"includeAverageCpc": "Request average cost-per-click metrics when available.",
|
|
284
|
+
"includeDeviceBreakdown": "Include device-specific planning metrics when supported.",
|
|
285
|
+
"includeKeywordConcepts": "Include Google's keyword concept grouping metadata.",
|
|
286
|
+
"startYearMonth": "First year and month of the historical metrics period.",
|
|
287
|
+
"endYearMonth": "Last year and month of the historical metrics period.",
|
|
288
|
+
"includeDismissed": "Include recommendations already dismissed in the account.",
|
|
289
|
+
"operation": "Exact allowlisted operation to execute. Arbitrary provider operations are not accepted.",
|
|
290
|
+
"category": "Category selected from the provider dictionary or field catalogue.",
|
|
291
|
+
"selectable": "Filter the field catalogue by whether the field can appear in SELECT.",
|
|
292
|
+
"filterable": "Filter the field catalogue by whether the field can appear in WHERE.",
|
|
293
|
+
"sortable": "Filter the field catalogue by whether the field can appear in ORDER BY.",
|
|
294
|
+
"pageToken": "Opaque continuation token returned by the preceding provider response.",
|
|
295
|
+
"locationNames": "Human-readable location names for Google to resolve into geo-target constants.",
|
|
296
|
+
"locale": "Locale used for human-readable lookup results.",
|
|
297
|
+
"resource": "Native Google Ads resource from which the GAQL query selects rows.",
|
|
298
|
+
"cursor": "Opaque pagination cursor returned by the preceding response; omit for the first page.",
|
|
299
|
+
"kind": "Entity or request kind whose schema or data is being requested.",
|
|
300
|
+
"startTime": "Beginning of the reporting period as an ISO 8601 timestamp with an explicit timezone.",
|
|
301
|
+
"endTime": "End of the reporting period as an ISO 8601 timestamp with an explicit timezone, after startTime.",
|
|
302
|
+
"reportDimension": "Native dimension used to break down the Snapchat report."
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/tool-quality.ts
|
|
306
|
+
import { z as z2 } from "zod";
|
|
307
|
+
var RESULT_SCHEMA = {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
result: {
|
|
311
|
+
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.",
|
|
312
|
+
type: ["object", "array", "string", "number", "boolean", "null"]
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
required: ["result"],
|
|
316
|
+
additionalProperties: true
|
|
317
|
+
};
|
|
318
|
+
function toolAnnotations(write) {
|
|
319
|
+
return { readOnlyHint: !write, destructiveHint: write, idempotentHint: !write, openWorldHint: true };
|
|
320
|
+
}
|
|
321
|
+
function structuredResult(value) {
|
|
322
|
+
if (value.isError) return value;
|
|
323
|
+
let result = value.content;
|
|
324
|
+
const first = value.content[0];
|
|
325
|
+
if (value.content.length === 1 && first?.type === "text" && typeof first.text === "string") {
|
|
326
|
+
result = first.text;
|
|
327
|
+
try {
|
|
328
|
+
result = JSON.parse(first.text);
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return { ...value, structuredContent: { ...value.structuredContent, result } };
|
|
333
|
+
}
|
|
334
|
+
var resultShape = { result: z2.union([z2.object({}).passthrough(), z2.array(z2.unknown()), z2.string(), z2.number(), z2.boolean(), z2.null()]).describe(RESULT_SCHEMA.properties.result.description) };
|
|
335
|
+
function installToolQuality(server) {
|
|
336
|
+
server.tool = ((name, description, inputSchema, handler) => {
|
|
337
|
+
const write = Object.hasOwn(inputSchema, "confirm");
|
|
338
|
+
inputSchema = Object.fromEntries(Object.entries(inputSchema).map(([key, schema]) => [key, schema.description || PARAMETER_DESCRIPTIONS[key] ? schema.describe(schema.description || PARAMETER_DESCRIPTIONS[key]) : schema]));
|
|
339
|
+
return server.registerTool(name, {
|
|
340
|
+
title: name.replace(/_/g, " "),
|
|
341
|
+
description,
|
|
342
|
+
inputSchema,
|
|
343
|
+
outputSchema: resultShape,
|
|
344
|
+
annotations: toolAnnotations(write)
|
|
345
|
+
}, async (...args) => structuredResult(await handler(...args)));
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
67
349
|
// src/server.ts
|
|
68
350
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
69
351
|
|
|
70
352
|
// src/platforms/gsc/tools.ts
|
|
71
|
-
import { z as
|
|
353
|
+
import { z as z3 } from "zod";
|
|
72
354
|
|
|
73
355
|
// src/core/errors.ts
|
|
74
356
|
var PlatformApiError = class extends Error {
|
|
@@ -83,13 +365,6 @@ var PlatformApiError = class extends Error {
|
|
|
83
365
|
this.retryAfter = retryAfter;
|
|
84
366
|
this.name = "PlatformApiError";
|
|
85
367
|
}
|
|
86
|
-
platform;
|
|
87
|
-
code;
|
|
88
|
-
isRateLimit;
|
|
89
|
-
isAuth;
|
|
90
|
-
isPermission;
|
|
91
|
-
suggestion;
|
|
92
|
-
retryAfter;
|
|
93
368
|
toMcpError() {
|
|
94
369
|
return {
|
|
95
370
|
error: this.message,
|
|
@@ -252,6 +527,33 @@ function flattenSearchAnalyticsResponse(response, dimensions) {
|
|
|
252
527
|
}
|
|
253
528
|
|
|
254
529
|
// src/platforms/gsc/gscClient.ts
|
|
530
|
+
function gscRouteClass(rawUrl) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = new URL(rawUrl);
|
|
533
|
+
if (parsed.pathname.endsWith("/searchAnalytics/query")) return "search_analytics";
|
|
534
|
+
if (parsed.pathname.endsWith("/urlInspection/index:inspect")) return "url_inspection";
|
|
535
|
+
if (parsed.pathname.includes("/sitemaps")) return "sitemaps";
|
|
536
|
+
if (parsed.pathname.includes("/sites")) return "sites";
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
return "gsc_api";
|
|
540
|
+
}
|
|
541
|
+
function codeOwnedApiFailure(status) {
|
|
542
|
+
if (status === 401 || status === 403) {
|
|
543
|
+
return { message: "GSC API authorization failed.", status: "AUTHORIZATION_FAILED" };
|
|
544
|
+
}
|
|
545
|
+
if (status === 429) {
|
|
546
|
+
return { message: "GSC API rate limit was reached.", status: "RATE_LIMITED" };
|
|
547
|
+
}
|
|
548
|
+
if (status >= 500) {
|
|
549
|
+
return { message: "GSC API is temporarily unavailable.", status: "UPSTREAM_UNAVAILABLE" };
|
|
550
|
+
}
|
|
551
|
+
return { message: "GSC API rejected the request.", status: "REQUEST_REJECTED" };
|
|
552
|
+
}
|
|
553
|
+
var GSC_AUTO_PAGINATION_MAX_PAGES = 4;
|
|
554
|
+
var GSC_AUTO_PAGINATION_MAX_ROWS = 1e5;
|
|
555
|
+
var GSC_AUTO_PAGINATION_DEADLINE_MS = 3e4;
|
|
556
|
+
var GSC_SINGLE_REQUEST_TIMEOUT_MS = 9e4;
|
|
255
557
|
function getAuthorizationUrl(clientId, redirectUri, state) {
|
|
256
558
|
const params = new URLSearchParams({
|
|
257
559
|
client_id: clientId,
|
|
@@ -325,16 +627,16 @@ var GSCClient = class {
|
|
|
325
627
|
// ============================================
|
|
326
628
|
// PRIVATE METHODS
|
|
327
629
|
// ============================================
|
|
328
|
-
async request(url, options = {}) {
|
|
630
|
+
async request(url, options = {}, timeoutMs = GSC_SINGLE_REQUEST_TIMEOUT_MS) {
|
|
631
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
632
|
+
throw new GSCApiException("GSC request deadline expired before dispatch", 408, "TIMEOUT");
|
|
633
|
+
}
|
|
329
634
|
const controller = new AbortController();
|
|
330
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
635
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
331
636
|
try {
|
|
332
637
|
const response = await fetch(url, {
|
|
333
638
|
...options,
|
|
334
639
|
signal: controller.signal,
|
|
335
|
-
// Forced after the spread: once a bearer token is attached, a redirect
|
|
336
|
-
// must never be followed, or the credential would be forwarded to
|
|
337
|
-
// whatever host the redirect names.
|
|
338
640
|
redirect: "error",
|
|
339
641
|
headers: {
|
|
340
642
|
Authorization: `Bearer ${this.accessToken}`,
|
|
@@ -343,21 +645,22 @@ var GSCClient = class {
|
|
|
343
645
|
}
|
|
344
646
|
});
|
|
345
647
|
if (!response.ok) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
648
|
+
const failure = codeOwnedApiFailure(response.status);
|
|
649
|
+
console.error("[GSCClient] API error", {
|
|
650
|
+
status: response.status,
|
|
651
|
+
route: gscRouteClass(url)
|
|
652
|
+
});
|
|
350
653
|
throw new GSCApiException(
|
|
351
|
-
|
|
654
|
+
failure.message,
|
|
352
655
|
response.status,
|
|
353
|
-
|
|
656
|
+
failure.status
|
|
354
657
|
);
|
|
355
658
|
}
|
|
356
|
-
return response.json();
|
|
659
|
+
return await response.json();
|
|
357
660
|
} catch (error) {
|
|
358
661
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
359
662
|
throw new GSCApiException(
|
|
360
|
-
"GSC API request
|
|
663
|
+
"GSC API request exceeded its bounded deadline",
|
|
361
664
|
408,
|
|
362
665
|
"TIMEOUT"
|
|
363
666
|
);
|
|
@@ -407,39 +710,80 @@ var GSCClient = class {
|
|
|
407
710
|
const url = `${GSC_API_BASE}/webmasters/v3/sites/${encodedSiteUrl}/searchAnalytics/query`;
|
|
408
711
|
const MAX_ROWS_PER_REQUEST = 25e3;
|
|
409
712
|
const rowLimit = request.rowLimit ?? MAX_ROWS_PER_REQUEST;
|
|
410
|
-
const
|
|
713
|
+
const autoPaginate = request.rowLimit === void 0;
|
|
714
|
+
const paginationDeadline = Date.now() + GSC_AUTO_PAGINATION_DEADLINE_MS;
|
|
715
|
+
const remainingPaginationMs = () => {
|
|
716
|
+
const remaining = paginationDeadline - Date.now();
|
|
717
|
+
if (remaining < 1) {
|
|
718
|
+
throw new GSCApiException(
|
|
719
|
+
"GSC auto-pagination exceeded its 30-second total deadline",
|
|
720
|
+
408,
|
|
721
|
+
"PAGINATION_DEADLINE"
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return remaining;
|
|
725
|
+
};
|
|
411
726
|
const firstResponse = await this.request(url, {
|
|
412
727
|
method: "POST",
|
|
413
728
|
body: JSON.stringify({
|
|
414
729
|
...request,
|
|
415
730
|
rowLimit: Math.min(rowLimit, MAX_ROWS_PER_REQUEST),
|
|
416
|
-
startRow:
|
|
731
|
+
startRow: request.startRow ?? 0
|
|
417
732
|
})
|
|
418
|
-
});
|
|
733
|
+
}, autoPaginate ? remainingPaginationMs() : GSC_SINGLE_REQUEST_TIMEOUT_MS);
|
|
734
|
+
if (autoPaginate) remainingPaginationMs();
|
|
419
735
|
const returnedRows = firstResponse.rows?.length ?? 0;
|
|
420
736
|
if (request.rowLimit || returnedRows < MAX_ROWS_PER_REQUEST) {
|
|
421
|
-
return
|
|
737
|
+
return {
|
|
738
|
+
...firstResponse,
|
|
739
|
+
pagination: {
|
|
740
|
+
mode: autoPaginate ? "bounded_auto" : "single_page",
|
|
741
|
+
pageCount: 1,
|
|
742
|
+
rowCount: returnedRows,
|
|
743
|
+
maximumPages: autoPaginate ? GSC_AUTO_PAGINATION_MAX_PAGES : 1,
|
|
744
|
+
maximumRows: autoPaginate ? GSC_AUTO_PAGINATION_MAX_ROWS : rowLimit,
|
|
745
|
+
deadlineMs: autoPaginate ? GSC_AUTO_PAGINATION_DEADLINE_MS : GSC_SINGLE_REQUEST_TIMEOUT_MS,
|
|
746
|
+
truncated: false
|
|
747
|
+
}
|
|
748
|
+
};
|
|
422
749
|
}
|
|
423
750
|
const allRows = [...firstResponse.rows ?? []];
|
|
424
|
-
let currentOffset =
|
|
425
|
-
|
|
751
|
+
let currentOffset = (request.startRow ?? 0) + returnedRows;
|
|
752
|
+
let pageCount = 1;
|
|
753
|
+
let lastPageWasFull = returnedRows === MAX_ROWS_PER_REQUEST;
|
|
754
|
+
while (pageCount < GSC_AUTO_PAGINATION_MAX_PAGES && allRows.length < GSC_AUTO_PAGINATION_MAX_ROWS) {
|
|
755
|
+
const remainingRows = GSC_AUTO_PAGINATION_MAX_ROWS - allRows.length;
|
|
756
|
+
const requestedRows = Math.min(MAX_ROWS_PER_REQUEST, remainingRows);
|
|
426
757
|
const pageResponse = await this.request(url, {
|
|
427
758
|
method: "POST",
|
|
428
759
|
body: JSON.stringify({
|
|
429
760
|
...request,
|
|
430
|
-
rowLimit:
|
|
761
|
+
rowLimit: requestedRows,
|
|
431
762
|
startRow: currentOffset
|
|
432
763
|
})
|
|
433
|
-
});
|
|
764
|
+
}, remainingPaginationMs());
|
|
765
|
+
remainingPaginationMs();
|
|
434
766
|
const pageRows = pageResponse.rows?.length ?? 0;
|
|
767
|
+
pageCount += 1;
|
|
768
|
+
lastPageWasFull = pageRows === requestedRows;
|
|
435
769
|
if (pageRows === 0) break;
|
|
436
|
-
allRows.push(...pageResponse.rows ?? []);
|
|
770
|
+
allRows.push(...(pageResponse.rows ?? []).slice(0, remainingRows));
|
|
437
771
|
currentOffset += pageRows;
|
|
438
|
-
if (pageRows <
|
|
772
|
+
if (pageRows < requestedRows) break;
|
|
439
773
|
}
|
|
774
|
+
remainingPaginationMs();
|
|
440
775
|
return {
|
|
441
776
|
...firstResponse,
|
|
442
|
-
rows: allRows
|
|
777
|
+
rows: allRows.slice(0, GSC_AUTO_PAGINATION_MAX_ROWS),
|
|
778
|
+
pagination: {
|
|
779
|
+
mode: "bounded_auto",
|
|
780
|
+
pageCount,
|
|
781
|
+
rowCount: Math.min(allRows.length, GSC_AUTO_PAGINATION_MAX_ROWS),
|
|
782
|
+
maximumPages: GSC_AUTO_PAGINATION_MAX_PAGES,
|
|
783
|
+
maximumRows: GSC_AUTO_PAGINATION_MAX_ROWS,
|
|
784
|
+
deadlineMs: GSC_AUTO_PAGINATION_DEADLINE_MS,
|
|
785
|
+
truncated: lastPageWasFull && (pageCount >= GSC_AUTO_PAGINATION_MAX_PAGES || allRows.length >= GSC_AUTO_PAGINATION_MAX_ROWS)
|
|
786
|
+
}
|
|
443
787
|
};
|
|
444
788
|
}
|
|
445
789
|
// ============================================
|
|
@@ -880,36 +1224,37 @@ function planGSCQuery(params) {
|
|
|
880
1224
|
}
|
|
881
1225
|
|
|
882
1226
|
// src/platforms/gsc/tools.ts
|
|
883
|
-
var siteUrlSchema =
|
|
884
|
-
var datePresetSchema =
|
|
885
|
-
var searchTypeSchema =
|
|
886
|
-
var dataStateSchema =
|
|
887
|
-
var aggregationTypeSchema =
|
|
888
|
-
var
|
|
889
|
-
var
|
|
890
|
-
|
|
891
|
-
|
|
1227
|
+
var siteUrlSchema = z3.string().optional().describe("Search Console property URL, e.g. https://example.com/ or sc-domain:example.com. Uses GSC_SITE_URL when omitted.");
|
|
1228
|
+
var datePresetSchema = z3.enum(["today", "yesterday", "last7days", "last28days", "last3months", "last6months", "last12months", "last16months"]);
|
|
1229
|
+
var searchTypeSchema = z3.enum(["web", "image", "video", "news", "discover", "googleNews"]);
|
|
1230
|
+
var dataStateSchema = z3.enum(["final", "all", "hourly_all"]);
|
|
1231
|
+
var aggregationTypeSchema = z3.enum(["auto", "byPage", "byProperty", "byNewsShowcasePanel"]);
|
|
1232
|
+
var CompareSearchTypesSchema = z3.array(searchTypeSchema).min(1).max(6).refine((items) => new Set(items).size === items.length, "searchTypes must be unique");
|
|
1233
|
+
var limitSchema = z3.number().int().min(1).max(25e3).optional().default(1e3);
|
|
1234
|
+
var dateRangeSchema = z3.object({
|
|
1235
|
+
startDate: z3.string().describe("Start date YYYY-MM-DD"),
|
|
1236
|
+
endDate: z3.string().describe("End date YYYY-MM-DD")
|
|
892
1237
|
});
|
|
893
|
-
var comparisonDimensionSchema =
|
|
894
|
-
var sitemapBaselineSchema =
|
|
895
|
-
path:
|
|
896
|
-
submitted:
|
|
897
|
-
indexed:
|
|
898
|
-
warnings:
|
|
899
|
-
errors:
|
|
900
|
-
lastSubmitted:
|
|
901
|
-
lastDownloaded:
|
|
1238
|
+
var comparisonDimensionSchema = z3.enum(["query", "page"]);
|
|
1239
|
+
var sitemapBaselineSchema = z3.object({
|
|
1240
|
+
path: z3.string().describe("Sitemap URL/path returned by GSC."),
|
|
1241
|
+
submitted: z3.number().int().min(0).optional().default(0),
|
|
1242
|
+
indexed: z3.number().int().min(0).optional().default(0),
|
|
1243
|
+
warnings: z3.number().int().min(0).optional().default(0),
|
|
1244
|
+
errors: z3.number().int().min(0).optional().default(0),
|
|
1245
|
+
lastSubmitted: z3.string().optional(),
|
|
1246
|
+
lastDownloaded: z3.string().optional()
|
|
902
1247
|
});
|
|
903
|
-
var watchlistUrlSchema =
|
|
904
|
-
url:
|
|
905
|
-
label:
|
|
906
|
-
priority:
|
|
907
|
-
tags:
|
|
908
|
-
expectedVerdict:
|
|
909
|
-
expectedCanonical:
|
|
910
|
-
expectedCoverageState:
|
|
1248
|
+
var watchlistUrlSchema = z3.object({
|
|
1249
|
+
url: z3.string().url(),
|
|
1250
|
+
label: z3.string().optional(),
|
|
1251
|
+
priority: z3.enum(["low", "medium", "high", "critical"]).optional().default("medium"),
|
|
1252
|
+
tags: z3.array(z3.string()).optional().default([]),
|
|
1253
|
+
expectedVerdict: z3.enum(["PASS", "PARTIAL", "FAIL", "NEUTRAL", "VERDICT_UNSPECIFIED"]).optional().default("PASS"),
|
|
1254
|
+
expectedCanonical: z3.string().url().optional(),
|
|
1255
|
+
expectedCoverageState: z3.string().optional()
|
|
911
1256
|
});
|
|
912
|
-
var samplingObjectiveSchema =
|
|
1257
|
+
var samplingObjectiveSchema = z3.enum(["topTraffic", "lowCtr", "declining", "sitemapRisk", "staleSitemaps"]);
|
|
913
1258
|
var BULK_URL_INSPECTION_LIMIT = 10;
|
|
914
1259
|
var URL_INSPECTION_DAILY_QUOTA = 2e3;
|
|
915
1260
|
var DEFAULT_FRESHNESS_LAG_WARNING_DAYS = 3;
|
|
@@ -1040,7 +1385,6 @@ var AuthenticatedGSCClient = class {
|
|
|
1040
1385
|
this.tokenExpiresAt = Date.now() + 45 * 60 * 1e3;
|
|
1041
1386
|
}
|
|
1042
1387
|
}
|
|
1043
|
-
config;
|
|
1044
1388
|
accessToken;
|
|
1045
1389
|
tokenExpiresAt = 0;
|
|
1046
1390
|
rateLimiter = new RateLimiter();
|
|
@@ -1570,26 +1914,26 @@ function registerGSCTools(server, config) {
|
|
|
1570
1914
|
Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for available fields and rules.`,
|
|
1571
1915
|
{
|
|
1572
1916
|
siteUrl: siteUrlSchema,
|
|
1573
|
-
metrics:
|
|
1574
|
-
dimensions:
|
|
1575
|
-
filters:
|
|
1576
|
-
field:
|
|
1577
|
-
operator:
|
|
1578
|
-
value:
|
|
1917
|
+
metrics: z3.array(z3.string()).optional().describe("Native: clicks, impressions, ctr, position. Calculated: ctrOpportunity, clicksPerImpression, impressionShare, clickShare."),
|
|
1918
|
+
dimensions: z3.array(z3.string()).max(5).optional().describe("Up to 5 dimensions selected from query, page, country, device, date, hour, searchAppearance."),
|
|
1919
|
+
filters: z3.array(z3.object({
|
|
1920
|
+
field: z3.string(),
|
|
1921
|
+
operator: z3.enum(["contains", "equals", "notContains", "notEquals", "includingRegex", "excludingRegex"]),
|
|
1922
|
+
value: z3.union([z3.string(), z3.number(), z3.boolean()])
|
|
1579
1923
|
})).optional(),
|
|
1580
1924
|
searchType: searchTypeSchema.optional().default("web"),
|
|
1581
1925
|
dataState: dataStateSchema.optional().default("all").describe("final=stable data, all=fresh daily data, hourly_all=fresh hourly data (use hour dimension)."),
|
|
1582
1926
|
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."),
|
|
1583
1927
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
1584
|
-
dateRange:
|
|
1585
|
-
startDate:
|
|
1586
|
-
endDate:
|
|
1928
|
+
dateRange: z3.object({
|
|
1929
|
+
startDate: z3.string().describe("Start date YYYY-MM-DD"),
|
|
1930
|
+
endDate: z3.string().describe("End date YYYY-MM-DD")
|
|
1587
1931
|
}).optional(),
|
|
1588
|
-
orderBy:
|
|
1589
|
-
orderDirection:
|
|
1932
|
+
orderBy: z3.enum(["clicks", "impressions", "ctr", "position", "ctrOpportunity", "clicksPerImpression", "impressionShare", "clickShare"]).optional().default("clicks"),
|
|
1933
|
+
orderDirection: z3.enum(["ASC", "DESC"]).optional().default("DESC"),
|
|
1590
1934
|
limit: limitSchema,
|
|
1591
|
-
startRow:
|
|
1592
|
-
autoPaginate:
|
|
1935
|
+
startRow: z3.number().int().min(0).optional().describe("Optional 0-based row offset for explicit pagination."),
|
|
1936
|
+
autoPaginate: z3.boolean().optional().default(false).describe("When true, omit rowLimit so the GSC client auto-paginates until exhaustion.")
|
|
1593
1937
|
},
|
|
1594
1938
|
async ({ siteUrl, metrics, dimensions, filters, searchType, dataState, aggregationType, datePreset, dateRange, orderBy, orderDirection, limit, startRow, autoPaginate }) => {
|
|
1595
1939
|
try {
|
|
@@ -1639,6 +1983,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1639
1983
|
mode: autoPaginate ? "auto_startRow" : "single_page",
|
|
1640
1984
|
autoPaginate,
|
|
1641
1985
|
startRow: startRow ?? 0,
|
|
1986
|
+
pageCount: response.pagination?.pageCount ?? 1,
|
|
1987
|
+
maximumPages: response.pagination?.maximumPages ?? 1,
|
|
1988
|
+
maximumRows: response.pagination?.maximumRows ?? limit,
|
|
1989
|
+
deadlineMs: response.pagination?.deadlineMs ?? 9e4,
|
|
1990
|
+
truncated: response.pagination?.truncated ?? false,
|
|
1642
1991
|
limit: autoPaginate ? null : limit
|
|
1643
1992
|
},
|
|
1644
1993
|
dataState,
|
|
@@ -1655,8 +2004,8 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1655
2004
|
"Inspect a URL for Google indexing status, crawl info, mobile usability, AMP, and rich results. Quota: 2000 requests/day/property.",
|
|
1656
2005
|
{
|
|
1657
2006
|
siteUrl: siteUrlSchema,
|
|
1658
|
-
url:
|
|
1659
|
-
languageCode:
|
|
2007
|
+
url: z3.string().url().describe("Full URL to inspect."),
|
|
2008
|
+
languageCode: z3.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR.")
|
|
1660
2009
|
},
|
|
1661
2010
|
async ({ siteUrl, url, languageCode }) => {
|
|
1662
2011
|
try {
|
|
@@ -1681,9 +2030,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1681
2030
|
"Inspect multiple URLs with a conservative sequential limit and normalized URL Inspection results. Quota: 2000 requests/day/property.",
|
|
1682
2031
|
{
|
|
1683
2032
|
siteUrl: siteUrlSchema,
|
|
1684
|
-
urls:
|
|
1685
|
-
languageCode:
|
|
1686
|
-
continueOnError:
|
|
2033
|
+
urls: z3.array(z3.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.`),
|
|
2034
|
+
languageCode: z3.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR."),
|
|
2035
|
+
continueOnError: z3.boolean().optional().default(true).describe("When true, returns per-URL errors instead of failing the entire batch.")
|
|
1687
2036
|
},
|
|
1688
2037
|
async ({ siteUrl, urls, languageCode, continueOnError }) => {
|
|
1689
2038
|
try {
|
|
@@ -1723,7 +2072,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1723
2072
|
"List sitemaps submitted for a Search Console property, including processing status and submitted/indexed counts.",
|
|
1724
2073
|
{
|
|
1725
2074
|
siteUrl: siteUrlSchema,
|
|
1726
|
-
sitemapIndex:
|
|
2075
|
+
sitemapIndex: z3.string().url().optional().describe("Optional sitemap index URL used to return only its child sitemaps.")
|
|
1727
2076
|
},
|
|
1728
2077
|
async ({ siteUrl, sitemapIndex }) => {
|
|
1729
2078
|
try {
|
|
@@ -1751,7 +2100,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1751
2100
|
"Get one submitted sitemap by full feed path, including type, fetch state, warnings/errors, and submitted/indexed content totals.",
|
|
1752
2101
|
{
|
|
1753
2102
|
siteUrl: siteUrlSchema,
|
|
1754
|
-
feedpath:
|
|
2103
|
+
feedpath: z3.string().url().describe("Full sitemap URL exactly as submitted to Search Console.")
|
|
1755
2104
|
},
|
|
1756
2105
|
async ({ siteUrl, feedpath }) => {
|
|
1757
2106
|
try {
|
|
@@ -1828,9 +2177,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1828
2177
|
"Compare performance across Search Console search types: web, image, video, news, discover, and Google News.",
|
|
1829
2178
|
{
|
|
1830
2179
|
siteUrl: siteUrlSchema,
|
|
1831
|
-
searchTypes:
|
|
2180
|
+
searchTypes: CompareSearchTypesSchema.optional().default(["web", "image", "video", "discover"]),
|
|
1832
2181
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
1833
|
-
dateRange:
|
|
2182
|
+
dateRange: z3.object({ startDate: z3.string(), endDate: z3.string() }).optional()
|
|
1834
2183
|
},
|
|
1835
2184
|
async ({ siteUrl, searchTypes, datePreset, dateRange }) => {
|
|
1836
2185
|
try {
|
|
@@ -1881,7 +2230,7 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1881
2230
|
{
|
|
1882
2231
|
siteUrl: siteUrlSchema,
|
|
1883
2232
|
searchType: searchTypeSchema.optional().default("web"),
|
|
1884
|
-
lookbackDays:
|
|
2233
|
+
lookbackDays: z3.number().int().min(3).max(30).optional().default(14).describe("Recent days to scan for daily data. Max 30.")
|
|
1885
2234
|
},
|
|
1886
2235
|
async ({ siteUrl, searchType, lookbackDays }) => {
|
|
1887
2236
|
try {
|
|
@@ -1939,11 +2288,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
1939
2288
|
"Read-only monitoring snapshot for Search Analytics freshness, sitemap indexation totals, and an optional small URL Inspection sample.",
|
|
1940
2289
|
{
|
|
1941
2290
|
siteUrl: siteUrlSchema,
|
|
1942
|
-
searchTypes:
|
|
1943
|
-
lookbackDays:
|
|
1944
|
-
freshnessLagWarningDays:
|
|
1945
|
-
inspectUrls:
|
|
1946
|
-
languageCode:
|
|
2291
|
+
searchTypes: z3.array(searchTypeSchema).min(1).max(6).optional().default(["web"]),
|
|
2292
|
+
lookbackDays: z3.number().int().min(3).max(30).optional().default(14).describe("Recent days to scan for Search Analytics freshness."),
|
|
2293
|
+
freshnessLagWarningDays: z3.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."),
|
|
2294
|
+
inspectUrls: z3.array(z3.string().url()).max(BULK_URL_INSPECTION_LIMIT).optional().default([]).describe(`Optional URLs to inspect. Maximum ${BULK_URL_INSPECTION_LIMIT} to protect URL Inspection quota.`),
|
|
2295
|
+
languageCode: z3.string().optional().describe("Optional IETF language tag for URL Inspection, e.g. en-US or fr-FR.")
|
|
1947
2296
|
},
|
|
1948
2297
|
async ({ siteUrl, searchTypes, lookbackDays, freshnessLagWarningDays, inspectUrls, languageCode }) => {
|
|
1949
2298
|
try {
|
|
@@ -2046,9 +2395,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2046
2395
|
"Read-only sitemap delta tracker. Fetches the current sitemap snapshot and compares it to an agent-provided baseline; no state is written.",
|
|
2047
2396
|
{
|
|
2048
2397
|
siteUrl: siteUrlSchema,
|
|
2049
|
-
baseline:
|
|
2050
|
-
includeUnchanged:
|
|
2051
|
-
minAbsIndexedDelta:
|
|
2398
|
+
baseline: z3.array(sitemapBaselineSchema).optional().default([]).describe("Previous snapshot entries from this tool's snapshot output."),
|
|
2399
|
+
includeUnchanged: z3.boolean().optional().default(false),
|
|
2400
|
+
minAbsIndexedDelta: z3.number().int().min(0).optional().default(1).describe("Minimum absolute indexed URL delta to include otherwise unchanged sitemaps.")
|
|
2052
2401
|
},
|
|
2053
2402
|
async ({ siteUrl, baseline, includeUnchanged, minAbsIndexedDelta }) => {
|
|
2054
2403
|
try {
|
|
@@ -2143,11 +2492,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2143
2492
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2144
2493
|
currentDateRange: dateRangeSchema.optional().describe("Current period. Defaults to datePreset."),
|
|
2145
2494
|
previousDateRange: dateRangeSchema.optional().describe("Previous period. Defaults to the immediately preceding period with the same length."),
|
|
2146
|
-
includePages:
|
|
2147
|
-
limit:
|
|
2148
|
-
topN:
|
|
2149
|
-
minImpressions:
|
|
2150
|
-
sortBy:
|
|
2495
|
+
includePages: z3.boolean().optional().default(false).describe("When true, groups by searchAppearance + page for page-level rich result trends."),
|
|
2496
|
+
limit: z3.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2497
|
+
topN: z3.number().int().min(1).max(100).optional().default(50),
|
|
2498
|
+
minImpressions: z3.number().int().min(0).optional().default(0),
|
|
2499
|
+
sortBy: z3.enum(["currentClicks", "currentImpressions", "clicksDelta", "impressionsDelta", "positionDelta"]).optional().default("impressionsDelta")
|
|
2151
2500
|
},
|
|
2152
2501
|
async ({ siteUrl, searchType, datePreset, currentDateRange, previousDateRange, includePages, limit, topN, minImpressions, sortBy }) => {
|
|
2153
2502
|
try {
|
|
@@ -2257,11 +2606,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2257
2606
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2258
2607
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2259
2608
|
dateRange: dateRangeSchema.optional(),
|
|
2260
|
-
objectives:
|
|
2261
|
-
pageLimit:
|
|
2262
|
-
maxInspectionUrls:
|
|
2263
|
-
minImpressionsForLowCtr:
|
|
2264
|
-
staleSitemapDays:
|
|
2609
|
+
objectives: z3.array(samplingObjectiveSchema).min(1).max(5).optional().default(["topTraffic", "lowCtr", "declining", "sitemapRisk", "staleSitemaps"]),
|
|
2610
|
+
pageLimit: z3.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2611
|
+
maxInspectionUrls: z3.number().int().min(1).max(BULK_URL_INSPECTION_LIMIT).optional().default(BULK_URL_INSPECTION_LIMIT),
|
|
2612
|
+
minImpressionsForLowCtr: z3.number().int().min(1).optional().default(100),
|
|
2613
|
+
staleSitemapDays: z3.number().int().min(1).max(180).optional().default(14)
|
|
2265
2614
|
},
|
|
2266
2615
|
async ({ siteUrl, searchType, datePreset, dateRange, objectives, pageLimit, maxInspectionUrls, minImpressionsForLowCtr, staleSitemapDays }) => {
|
|
2267
2616
|
try {
|
|
@@ -2390,9 +2739,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2390
2739
|
"Read-only URL Inspection watchlist for critical URLs. Inspects up to 10 URLs and returns alert/review/ok assessments without storing state.",
|
|
2391
2740
|
{
|
|
2392
2741
|
siteUrl: siteUrlSchema,
|
|
2393
|
-
urls:
|
|
2394
|
-
languageCode:
|
|
2395
|
-
continueOnError:
|
|
2742
|
+
urls: z3.array(watchlistUrlSchema).min(1).max(BULK_URL_INSPECTION_LIMIT).describe(`Watchlist entries. Maximum ${BULK_URL_INSPECTION_LIMIT} per call to protect URL Inspection quota.`),
|
|
2743
|
+
languageCode: z3.string().optional().describe("Optional IETF language tag, e.g. en-US or fr-FR."),
|
|
2744
|
+
continueOnError: z3.boolean().optional().default(true)
|
|
2396
2745
|
},
|
|
2397
2746
|
async ({ siteUrl, urls, languageCode, continueOnError }) => {
|
|
2398
2747
|
try {
|
|
@@ -2459,10 +2808,10 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2459
2808
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2460
2809
|
currentDateRange: dateRangeSchema.optional().describe("Current period. Defaults to datePreset."),
|
|
2461
2810
|
previousDateRange: dateRangeSchema.optional().describe("Previous period. Defaults to the immediately preceding period with the same length."),
|
|
2462
|
-
limit:
|
|
2463
|
-
topN:
|
|
2464
|
-
minAbsClickDelta:
|
|
2465
|
-
sortBy:
|
|
2811
|
+
limit: z3.number().int().min(1).max(25e3).optional().default(5e3).describe("Rows to fetch for each period."),
|
|
2812
|
+
topN: z3.number().int().min(1).max(100).optional().default(25),
|
|
2813
|
+
minAbsClickDelta: z3.number().min(0).optional().default(0).describe("Filter out rows with smaller absolute click delta."),
|
|
2814
|
+
sortBy: z3.enum(["clicksDelta", "impressionsDelta", "ctrDelta", "positionDelta"]).optional().default("clicksDelta")
|
|
2466
2815
|
},
|
|
2467
2816
|
async ({ siteUrl, dimension, searchType, datePreset, currentDateRange, previousDateRange, limit, topN, minAbsClickDelta, sortBy }) => {
|
|
2468
2817
|
try {
|
|
@@ -2578,11 +2927,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2578
2927
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2579
2928
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2580
2929
|
dateRange: dateRangeSchema.optional(),
|
|
2581
|
-
brandTerms:
|
|
2582
|
-
categoryRules:
|
|
2583
|
-
limit:
|
|
2584
|
-
maxClusters:
|
|
2585
|
-
topQueriesPerCluster:
|
|
2930
|
+
brandTerms: z3.array(z3.string()).optional().default([]).describe("Optional brand terms. Defaults to terms derived from the property domain."),
|
|
2931
|
+
categoryRules: z3.record(z3.array(z3.string())).optional().describe("Optional map of category name to matching tokens/phrases."),
|
|
2932
|
+
limit: z3.number().int().min(1).max(25e3).optional().default(5e3),
|
|
2933
|
+
maxClusters: z3.number().int().min(1).max(100).optional().default(25),
|
|
2934
|
+
topQueriesPerCluster: z3.number().int().min(1).max(25).optional().default(10)
|
|
2586
2935
|
},
|
|
2587
2936
|
async ({ siteUrl, searchType, datePreset, dateRange, brandTerms, categoryRules, limit, maxClusters, topQueriesPerCluster }) => {
|
|
2588
2937
|
try {
|
|
@@ -2659,11 +3008,11 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2659
3008
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2660
3009
|
datePreset: datePresetSchema.optional().default("last28days"),
|
|
2661
3010
|
dateRange: dateRangeSchema.optional(),
|
|
2662
|
-
limit:
|
|
2663
|
-
minPages:
|
|
2664
|
-
minImpressions:
|
|
2665
|
-
maxTopPageClickShare:
|
|
2666
|
-
topN:
|
|
3011
|
+
limit: z3.number().int().min(1).max(25e3).optional().default(5e3),
|
|
3012
|
+
minPages: z3.number().int().min(2).max(10).optional().default(2),
|
|
3013
|
+
minImpressions: z3.number().int().min(0).optional().default(100),
|
|
3014
|
+
maxTopPageClickShare: z3.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."),
|
|
3015
|
+
topN: z3.number().int().min(1).max(100).optional().default(25)
|
|
2667
3016
|
},
|
|
2668
3017
|
async ({ siteUrl, searchType, datePreset, dateRange, limit, minPages, minImpressions, maxTopPageClickShare, topN }) => {
|
|
2669
3018
|
try {
|
|
@@ -2759,9 +3108,9 @@ Use gsc://metrics, gsc://dimensions, gsc://filters, and gsc://compatibility for
|
|
|
2759
3108
|
"gsc_validate_query",
|
|
2760
3109
|
"Validate a Search Console metric/dimension/search type combination before executing it.",
|
|
2761
3110
|
{
|
|
2762
|
-
dimensions:
|
|
3111
|
+
dimensions: z3.array(z3.string()).optional().describe("Dimensions to validate."),
|
|
2763
3112
|
searchType: searchTypeSchema.optional().default("web"),
|
|
2764
|
-
dateRange:
|
|
3113
|
+
dateRange: z3.object({ startDate: z3.string(), endDate: z3.string() }).optional()
|
|
2765
3114
|
},
|
|
2766
3115
|
async ({ dimensions, searchType, dateRange }) => {
|
|
2767
3116
|
try {
|
|
@@ -3044,12 +3393,13 @@ function registerGSC(server, config) {
|
|
|
3044
3393
|
}
|
|
3045
3394
|
|
|
3046
3395
|
// src/server.ts
|
|
3047
|
-
var PACKAGE_VERSION = "
|
|
3396
|
+
var PACKAGE_VERSION = "2.0.1";
|
|
3048
3397
|
function createServer(config) {
|
|
3049
3398
|
const server = new McpServer(
|
|
3050
|
-
{ name: "google-search-console-mcp", version: PACKAGE_VERSION },
|
|
3399
|
+
{ 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" }] },
|
|
3051
3400
|
{ capabilities: { tools: { listChanged: true }, resources: { subscribe: false, listChanged: true } } }
|
|
3052
3401
|
);
|
|
3402
|
+
installToolQuality(server);
|
|
3053
3403
|
registerGSC(server, config);
|
|
3054
3404
|
logger.system(`google-search-console-mcp v${PACKAGE_VERSION} ready, read-only`);
|
|
3055
3405
|
return server;
|