@yoryoboy/bi-mcp 1.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/README.md +346 -0
- package/bin/bi-mcp.js +2 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.js +768 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp-use.json +7 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/icon.svg +6 -0
- package/dist/src/analytics/ga4-channel-groups.js +20 -0
- package/dist/src/analytics/ga4-channel-groups.js.map +7 -0
- package/dist/src/analytics/ga4-report-utils.js +117 -0
- package/dist/src/analytics/ga4-report-utils.js.map +7 -0
- package/dist/src/config/benchmarks.js +128 -0
- package/dist/src/config/benchmarks.js.map +7 -0
- package/dist/src/config/google.js +41 -0
- package/dist/src/config/google.js.map +7 -0
- package/dist/src/config/vtex.js +26 -0
- package/dist/src/config/vtex.js.map +7 -0
- package/dist/src/google-ads/report-utils.js +78 -0
- package/dist/src/google-ads/report-utils.js.map +7 -0
- package/dist/src/prompts/reporte-ventas.js +75 -0
- package/dist/src/prompts/reporte-ventas.js.map +7 -0
- package/dist/src/search-console/search-console-utils.js +275 -0
- package/dist/src/search-console/search-console-utils.js.map +7 -0
- package/dist/src/services/analytics/ga4-client.js +69 -0
- package/dist/src/services/analytics/ga4-client.js.map +7 -0
- package/dist/src/services/analytics/oauth.js +30 -0
- package/dist/src/services/analytics/oauth.js.map +7 -0
- package/dist/src/services/google-ads/google-ads-client.js +54 -0
- package/dist/src/services/google-ads/google-ads-client.js.map +7 -0
- package/dist/src/services/search-console/search-console-client.js +45 -0
- package/dist/src/services/search-console/search-console-client.js.map +7 -0
- package/dist/src/services/vtex/vtex-api.js +51 -0
- package/dist/src/services/vtex/vtex-api.js.map +7 -0
- package/dist/src/services/vtex/vtex-catalog.js +18 -0
- package/dist/src/services/vtex/vtex-catalog.js.map +7 -0
- package/dist/src/services/vtex/vtex-logistics.js +151 -0
- package/dist/src/services/vtex/vtex-logistics.js.map +7 -0
- package/dist/src/services/vtex/vtex-orders.js +143 -0
- package/dist/src/services/vtex/vtex-orders.js.map +7 -0
- package/dist/src/services/vtex/vtex-pricing.js +17 -0
- package/dist/src/services/vtex/vtex-pricing.js.map +7 -0
- package/dist/src/tools/analytics/attribution-gaps.js +109 -0
- package/dist/src/tools/analytics/attribution-gaps.js.map +7 -0
- package/dist/src/tools/analytics/channel-mix.js +74 -0
- package/dist/src/tools/analytics/channel-mix.js.map +7 -0
- package/dist/src/tools/analytics/ecommerce-tracking-health.js +89 -0
- package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +7 -0
- package/dist/src/tools/analytics/engagement-overview.js +71 -0
- package/dist/src/tools/analytics/engagement-overview.js.map +7 -0
- package/dist/src/tools/analytics/index.js +12 -0
- package/dist/src/tools/analytics/index.js.map +7 -0
- package/dist/src/tools/analytics/list-accessible-properties.js +46 -0
- package/dist/src/tools/analytics/list-accessible-properties.js.map +7 -0
- package/dist/src/tools/analytics/property-info.js +54 -0
- package/dist/src/tools/analytics/property-info.js.map +7 -0
- package/dist/src/tools/analytics/revenue-by-channel.js +70 -0
- package/dist/src/tools/analytics/revenue-by-channel.js.map +7 -0
- package/dist/src/tools/analytics/revenue-overview.js +77 -0
- package/dist/src/tools/analytics/revenue-overview.js.map +7 -0
- package/dist/src/tools/analytics/revenue-trend.js +69 -0
- package/dist/src/tools/analytics/revenue-trend.js.map +7 -0
- package/dist/src/tools/analytics/source-medium-breakdown.js +86 -0
- package/dist/src/tools/analytics/source-medium-breakdown.js.map +7 -0
- package/dist/src/tools/analytics/top-landing-pages.js +79 -0
- package/dist/src/tools/analytics/top-landing-pages.js.map +7 -0
- package/dist/src/tools/google-ads/account-overview.js +103 -0
- package/dist/src/tools/google-ads/account-overview.js.map +7 -0
- package/dist/src/tools/google-ads/account-risks.js +267 -0
- package/dist/src/tools/google-ads/account-risks.js.map +7 -0
- package/dist/src/tools/google-ads/break-even-analysis.js +107 -0
- package/dist/src/tools/google-ads/break-even-analysis.js.map +7 -0
- package/dist/src/tools/google-ads/campaign-performance.js +157 -0
- package/dist/src/tools/google-ads/campaign-performance.js.map +7 -0
- package/dist/src/tools/google-ads/channel-mix.js +129 -0
- package/dist/src/tools/google-ads/channel-mix.js.map +7 -0
- package/dist/src/tools/google-ads/compare-accounts.js +122 -0
- package/dist/src/tools/google-ads/compare-accounts.js.map +7 -0
- package/dist/src/tools/google-ads/customer-clients.js +77 -0
- package/dist/src/tools/google-ads/customer-clients.js.map +7 -0
- package/dist/src/tools/google-ads/customer-info.js +64 -0
- package/dist/src/tools/google-ads/customer-info.js.map +7 -0
- package/dist/src/tools/google-ads/index.js +12 -0
- package/dist/src/tools/google-ads/index.js.map +7 -0
- package/dist/src/tools/google-ads/scaling-health.js +174 -0
- package/dist/src/tools/google-ads/scaling-health.js.map +7 -0
- package/dist/src/tools/google-ads/search-terms-summary.js +131 -0
- package/dist/src/tools/google-ads/search-terms-summary.js.map +7 -0
- package/dist/src/tools/google-ads/time-series.js +126 -0
- package/dist/src/tools/google-ads/time-series.js.map +7 -0
- package/dist/src/tools/index.js +5 -0
- package/dist/src/tools/index.js.map +7 -0
- package/dist/src/tools/search-console/country-breakdown.js +85 -0
- package/dist/src/tools/search-console/country-breakdown.js.map +7 -0
- package/dist/src/tools/search-console/device-breakdown.js +85 -0
- package/dist/src/tools/search-console/device-breakdown.js.map +7 -0
- package/dist/src/tools/search-console/high-impression-low-click-queries.js +95 -0
- package/dist/src/tools/search-console/high-impression-low-click-queries.js.map +7 -0
- package/dist/src/tools/search-console/index.js +15 -0
- package/dist/src/tools/search-console/index.js.map +7 -0
- package/dist/src/tools/search-console/list-accessible-sites.js +42 -0
- package/dist/src/tools/search-console/list-accessible-sites.js.map +7 -0
- package/dist/src/tools/search-console/low-ctr-opportunities.js +98 -0
- package/dist/src/tools/search-console/low-ctr-opportunities.js.map +7 -0
- package/dist/src/tools/search-console/page-performance.js +104 -0
- package/dist/src/tools/search-console/page-performance.js.map +7 -0
- package/dist/src/tools/search-console/product-demand-low-capture-queries.js +93 -0
- package/dist/src/tools/search-console/product-demand-low-capture-queries.js.map +7 -0
- package/dist/src/tools/search-console/query-page-matrix.js +99 -0
- package/dist/src/tools/search-console/query-page-matrix.js.map +7 -0
- package/dist/src/tools/search-console/query-performance.js +109 -0
- package/dist/src/tools/search-console/query-performance.js.map +7 -0
- package/dist/src/tools/search-console/quick-win-opportunities.js +93 -0
- package/dist/src/tools/search-console/quick-win-opportunities.js.map +7 -0
- package/dist/src/tools/search-console/rising-non-brand-queries.js +121 -0
- package/dist/src/tools/search-console/rising-non-brand-queries.js.map +7 -0
- package/dist/src/tools/search-console/search-performance.js +89 -0
- package/dist/src/tools/search-console/search-performance.js.map +7 -0
- package/dist/src/tools/search-console/site-context.js +43 -0
- package/dist/src/tools/search-console/site-context.js.map +7 -0
- package/dist/src/tools/search-console/visibility-declines.js +146 -0
- package/dist/src/tools/search-console/visibility-declines.js.map +7 -0
- package/dist/src/tools/vtex/computed-price.js +48 -0
- package/dist/src/tools/vtex/computed-price.js.map +7 -0
- package/dist/src/tools/vtex/index.js +11 -0
- package/dist/src/tools/vtex/index.js.map +7 -0
- package/dist/src/tools/vtex/inventory-check.js +148 -0
- package/dist/src/tools/vtex/inventory-check.js.map +7 -0
- package/dist/src/tools/vtex/order-details.js +56 -0
- package/dist/src/tools/vtex/order-details.js.map +7 -0
- package/dist/src/tools/vtex/orders-summary.js +83 -0
- package/dist/src/tools/vtex/orders-summary.js.map +7 -0
- package/dist/src/tools/vtex/product-offers.js +28 -0
- package/dist/src/tools/vtex/product-offers.js.map +7 -0
- package/dist/src/tools/vtex/sku-offers.js +30 -0
- package/dist/src/tools/vtex/sku-offers.js.map +7 -0
- package/dist/src/tools/vtex/sku-price.js +42 -0
- package/dist/src/tools/vtex/sku-price.js.map +7 -0
- package/dist/src/tools/vtex/update-inventory.js +43 -0
- package/dist/src/tools/vtex/update-inventory.js.map +7 -0
- package/dist/src/tools/vtex/update-lead-time.js +32 -0
- package/dist/src/tools/vtex/update-lead-time.js.map +7 -0
- package/dist/src/tools/vtex/warehouse-inventory.js +42 -0
- package/dist/src/tools/vtex/warehouse-inventory.js.map +7 -0
- package/dist/src/utils/case-conversion.js +21 -0
- package/dist/src/utils/case-conversion.js.map +7 -0
- package/dist/src/utils/currency.js +52 -0
- package/dist/src/utils/currency.js.map +7 -0
- package/dist/src/utils/format-order-details.js +137 -0
- package/dist/src/utils/format-order-details.js.map +7 -0
- package/dist/src/utils/google-ads.js +78 -0
- package/dist/src/utils/google-ads.js.map +7 -0
- package/dist/src/utils/money.js +83 -0
- package/dist/src/utils/money.js.map +7 -0
- package/dist/src/utils/order-status.js +11 -0
- package/dist/src/utils/order-status.js.map +7 -0
- package/dist/src/utils/pagination.js +45 -0
- package/dist/src/utils/pagination.js.map +7 -0
- package/dist/src/utils/strip-payload.js +40 -0
- package/dist/src/utils/strip-payload.js.map +7 -0
- package/dist/src/utils/type-guards.js +7 -0
- package/dist/src/utils/type-guards.js.map +7 -0
- package/package.json +66 -0
- package/public/favicon.ico +0 -0
- package/public/icon.svg +6 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { querySearchConsolePerformance } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
buildSearchConsoleQueryBody,
|
|
6
|
+
endDateSchemaField,
|
|
7
|
+
profileIdSchemaField,
|
|
8
|
+
resolveSearchConsoleSiteUrl,
|
|
9
|
+
searchTypeSchemaField,
|
|
10
|
+
siteUrlSchemaField,
|
|
11
|
+
startDateSchemaField,
|
|
12
|
+
sumSearchConsoleRows,
|
|
13
|
+
toPercent,
|
|
14
|
+
normalizeSearchConsoleRow,
|
|
15
|
+
round
|
|
16
|
+
} from "../../search-console/search-console-utils.js";
|
|
17
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
18
|
+
const searchConsoleDeviceBreakdownSchema = z.object({
|
|
19
|
+
startDate: startDateSchemaField,
|
|
20
|
+
endDate: endDateSchemaField,
|
|
21
|
+
siteUrl: siteUrlSchemaField,
|
|
22
|
+
profileId: profileIdSchemaField,
|
|
23
|
+
searchType: searchTypeSchemaField,
|
|
24
|
+
queryContains: z.string().optional().describe("Optional query substring filter."),
|
|
25
|
+
pageContains: z.string().optional().describe("Optional page substring filter.")
|
|
26
|
+
});
|
|
27
|
+
async function searchConsoleDeviceBreakdownHandler(params) {
|
|
28
|
+
try {
|
|
29
|
+
const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);
|
|
30
|
+
const dimensionFilters = [];
|
|
31
|
+
if (params.queryContains?.trim()) {
|
|
32
|
+
dimensionFilters.push({
|
|
33
|
+
dimension: "query",
|
|
34
|
+
operator: "contains",
|
|
35
|
+
expression: params.queryContains.trim()
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (params.pageContains?.trim()) {
|
|
39
|
+
dimensionFilters.push({
|
|
40
|
+
dimension: "page",
|
|
41
|
+
operator: "contains",
|
|
42
|
+
expression: params.pageContains.trim()
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const response = await querySearchConsolePerformance(
|
|
46
|
+
siteUrl,
|
|
47
|
+
buildSearchConsoleQueryBody({
|
|
48
|
+
startDate: params.startDate,
|
|
49
|
+
endDate: params.endDate,
|
|
50
|
+
dimensions: ["device"],
|
|
51
|
+
searchType: params.searchType,
|
|
52
|
+
dimensionFilters
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["device"]));
|
|
56
|
+
const totals = sumSearchConsoleRows(rows);
|
|
57
|
+
return object(
|
|
58
|
+
stripNulls({
|
|
59
|
+
site_url: siteUrl,
|
|
60
|
+
date_range: {
|
|
61
|
+
start_date: params.startDate,
|
|
62
|
+
end_date: params.endDate
|
|
63
|
+
},
|
|
64
|
+
device_breakdown: rows.map((row) => ({
|
|
65
|
+
device: row.dimensions.device,
|
|
66
|
+
clicks: row.clicks,
|
|
67
|
+
impressions: row.impressions,
|
|
68
|
+
ctr_percent: row.ctr_percent,
|
|
69
|
+
position: row.position,
|
|
70
|
+
impression_share_percent: round(toPercent(row.impressions, totals.impressions), 2),
|
|
71
|
+
click_share_percent: round(toPercent(row.clicks, totals.clicks), 2)
|
|
72
|
+
}))
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return error(
|
|
77
|
+
err instanceof Error ? err.message : "Failed to fetch Search Console device breakdown"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
searchConsoleDeviceBreakdownHandler,
|
|
83
|
+
searchConsoleDeviceBreakdownSchema
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=device-breakdown.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/device-breakdown.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { querySearchConsolePerformance } from \"../../services/search-console/search-console-client.js\";\nimport {\n buildSearchConsoleQueryBody,\n endDateSchemaField,\n profileIdSchemaField,\n resolveSearchConsoleSiteUrl,\n searchTypeSchemaField,\n siteUrlSchemaField,\n startDateSchemaField,\n sumSearchConsoleRows,\n toPercent,\n normalizeSearchConsoleRow,\n round,\n} from \"../../search-console/search-console-utils.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const searchConsoleDeviceBreakdownSchema = z.object({\n startDate: startDateSchemaField,\n endDate: endDateSchemaField,\n siteUrl: siteUrlSchemaField,\n profileId: profileIdSchemaField,\n searchType: searchTypeSchemaField,\n queryContains: z.string().optional().describe(\"Optional query substring filter.\"),\n pageContains: z.string().optional().describe(\"Optional page substring filter.\"),\n});\n\nexport async function searchConsoleDeviceBreakdownHandler(\n params: z.infer<typeof searchConsoleDeviceBreakdownSchema>\n) {\n try {\n const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);\n const dimensionFilters = [];\n\n if (params.queryContains?.trim()) {\n dimensionFilters.push({\n dimension: \"query\",\n operator: \"contains\",\n expression: params.queryContains.trim(),\n });\n }\n\n if (params.pageContains?.trim()) {\n dimensionFilters.push({\n dimension: \"page\",\n operator: \"contains\",\n expression: params.pageContains.trim(),\n });\n }\n\n const response = await querySearchConsolePerformance(\n siteUrl,\n buildSearchConsoleQueryBody({\n startDate: params.startDate,\n endDate: params.endDate,\n dimensions: [\"device\"],\n searchType: params.searchType,\n dimensionFilters,\n })\n );\n\n const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, [\"device\"]));\n const totals = sumSearchConsoleRows(rows);\n\n return object(\n stripNulls({\n site_url: siteUrl,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n device_breakdown: rows.map((row) => ({\n device: row.dimensions.device,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr_percent: row.ctr_percent,\n position: row.position,\n impression_share_percent: round(toPercent(row.impressions, totals.impressions), 2),\n click_share_percent: round(toPercent(row.clicks, totals.clicks), 2),\n })),\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to fetch Search Console device breakdown\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAEpB,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kCAAkC;AAAA,EAChF,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,iCAAiC;AAChF,CAAC;AAED,eAAsB,oCACpB,QACA;AACA,MAAI;AACF,UAAM,UAAU,4BAA4B,OAAO,SAAS,OAAO,SAAS;AAC5E,UAAM,mBAAmB,CAAC;AAE1B,QAAI,OAAO,eAAe,KAAK,GAAG;AAChC,uBAAiB,KAAK;AAAA,QACpB,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY,OAAO,cAAc,KAAK;AAAA,MACxC,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,cAAc,KAAK,GAAG;AAC/B,uBAAiB,KAAK;AAAA,QACpB,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY,OAAO,aAAa,KAAK;AAAA,MACvC,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,4BAA4B;AAAA,QAC1B,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,YAAY,CAAC,QAAQ;AAAA,QACrB,YAAY,OAAO;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,0BAA0B,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC1F,UAAM,SAAS,qBAAqB,IAAI;AAExC,WAAO;AAAA,MACL,WAAW;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,UACV,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,QACnB;AAAA,QACA,kBAAkB,KAAK,IAAI,CAAC,SAAS;AAAA,UACnC,QAAQ,IAAI,WAAW;AAAA,UACvB,QAAQ,IAAI;AAAA,UACZ,aAAa,IAAI;AAAA,UACjB,aAAa,IAAI;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,0BAA0B,MAAM,UAAU,IAAI,aAAa,OAAO,WAAW,GAAG,CAAC;AAAA,UACjF,qBAAqB,MAAM,UAAU,IAAI,QAAQ,OAAO,MAAM,GAAG,CAAC;AAAA,QACpE,EAAE;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { querySearchConsolePerformance } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
brandTermsSchemaField,
|
|
6
|
+
buildSearchConsoleQueryBody,
|
|
7
|
+
classifyBrandLabel,
|
|
8
|
+
endDateSchemaField,
|
|
9
|
+
profileIdSchemaField,
|
|
10
|
+
resolveSearchConsoleSiteUrl,
|
|
11
|
+
rowLimitSchemaField,
|
|
12
|
+
scoreHighImpressionLowClickOpportunity,
|
|
13
|
+
searchTypeSchemaField,
|
|
14
|
+
siteUrlSchemaField,
|
|
15
|
+
startDateSchemaField,
|
|
16
|
+
normalizeSearchConsoleRow
|
|
17
|
+
} from "../../search-console/search-console-utils.js";
|
|
18
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
19
|
+
const searchConsoleHighImpressionLowClickQueriesSchema = z.object({
|
|
20
|
+
startDate: startDateSchemaField,
|
|
21
|
+
endDate: endDateSchemaField,
|
|
22
|
+
siteUrl: siteUrlSchemaField,
|
|
23
|
+
profileId: profileIdSchemaField,
|
|
24
|
+
searchType: searchTypeSchemaField,
|
|
25
|
+
brandTerms: brandTermsSchemaField,
|
|
26
|
+
includeBrand: z.enum(["all", "only_brand", "only_non_brand"]).optional().describe("How to filter the query list when brandTerms are provided."),
|
|
27
|
+
minImpressions: z.number().min(1).optional().describe("Minimum impressions required to consider a query an opportunity."),
|
|
28
|
+
maxCtr: z.number().min(0).max(100).optional().describe("Maximum CTR percentage allowed to count as low capture."),
|
|
29
|
+
rowLimit: rowLimitSchemaField
|
|
30
|
+
});
|
|
31
|
+
async function searchConsoleHighImpressionLowClickQueriesHandler(params) {
|
|
32
|
+
try {
|
|
33
|
+
const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);
|
|
34
|
+
const minImpressions = params.minImpressions ?? 100;
|
|
35
|
+
const maxCtr = params.maxCtr ?? 3;
|
|
36
|
+
const includeBrand = params.includeBrand ?? "all";
|
|
37
|
+
const response = await querySearchConsolePerformance(
|
|
38
|
+
siteUrl,
|
|
39
|
+
buildSearchConsoleQueryBody({
|
|
40
|
+
startDate: params.startDate,
|
|
41
|
+
endDate: params.endDate,
|
|
42
|
+
dimensions: ["query"],
|
|
43
|
+
searchType: params.searchType,
|
|
44
|
+
rowLimit: params.rowLimit ?? 250
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
const opportunities = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["query"])).map((row) => ({
|
|
48
|
+
query: row.dimensions.query,
|
|
49
|
+
clicks: row.clicks,
|
|
50
|
+
impressions: row.impressions,
|
|
51
|
+
ctr_percent: row.ctr_percent,
|
|
52
|
+
position: row.position,
|
|
53
|
+
brand_classification: classifyBrandLabel(row.dimensions.query, params.brandTerms)
|
|
54
|
+
})).filter((row) => row.impressions >= minImpressions && row.ctr_percent <= maxCtr).filter((row) => {
|
|
55
|
+
if (includeBrand === "only_brand") {
|
|
56
|
+
return row.brand_classification === "brand";
|
|
57
|
+
}
|
|
58
|
+
if (includeBrand === "only_non_brand") {
|
|
59
|
+
return row.brand_classification === "non_brand";
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}).map((row) => ({
|
|
63
|
+
...row,
|
|
64
|
+
opportunity_score: scoreHighImpressionLowClickOpportunity({
|
|
65
|
+
impressions: row.impressions,
|
|
66
|
+
ctrPercent: row.ctr_percent,
|
|
67
|
+
position: row.position
|
|
68
|
+
}),
|
|
69
|
+
reason: "High impression volume with low click capture."
|
|
70
|
+
})).sort((left, right) => right.opportunity_score - left.opportunity_score);
|
|
71
|
+
return object(
|
|
72
|
+
stripNulls({
|
|
73
|
+
site_url: siteUrl,
|
|
74
|
+
date_range: {
|
|
75
|
+
start_date: params.startDate,
|
|
76
|
+
end_date: params.endDate
|
|
77
|
+
},
|
|
78
|
+
thresholds: {
|
|
79
|
+
min_impressions: minImpressions,
|
|
80
|
+
max_ctr_percent: maxCtr
|
|
81
|
+
},
|
|
82
|
+
opportunities
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return error(
|
|
87
|
+
err instanceof Error ? err.message : "Failed to find Search Console high-impression low-click queries"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
searchConsoleHighImpressionLowClickQueriesHandler,
|
|
93
|
+
searchConsoleHighImpressionLowClickQueriesSchema
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=high-impression-low-click-queries.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/high-impression-low-click-queries.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { querySearchConsolePerformance } from \"../../services/search-console/search-console-client.js\";\nimport {\n brandTermsSchemaField,\n buildSearchConsoleQueryBody,\n classifyBrandLabel,\n endDateSchemaField,\n profileIdSchemaField,\n resolveSearchConsoleSiteUrl,\n rowLimitSchemaField,\n scoreHighImpressionLowClickOpportunity,\n searchTypeSchemaField,\n siteUrlSchemaField,\n startDateSchemaField,\n normalizeSearchConsoleRow,\n} from \"../../search-console/search-console-utils.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const searchConsoleHighImpressionLowClickQueriesSchema = z.object({\n startDate: startDateSchemaField,\n endDate: endDateSchemaField,\n siteUrl: siteUrlSchemaField,\n profileId: profileIdSchemaField,\n searchType: searchTypeSchemaField,\n brandTerms: brandTermsSchemaField,\n includeBrand: z\n .enum([\"all\", \"only_brand\", \"only_non_brand\"])\n .optional()\n .describe(\"How to filter the query list when brandTerms are provided.\"),\n minImpressions: z\n .number()\n .min(1)\n .optional()\n .describe(\"Minimum impressions required to consider a query an opportunity.\"),\n maxCtr: z\n .number()\n .min(0)\n .max(100)\n .optional()\n .describe(\"Maximum CTR percentage allowed to count as low capture.\"),\n rowLimit: rowLimitSchemaField,\n});\n\nexport async function searchConsoleHighImpressionLowClickQueriesHandler(\n params: z.infer<typeof searchConsoleHighImpressionLowClickQueriesSchema>\n) {\n try {\n const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);\n const minImpressions = params.minImpressions ?? 100;\n const maxCtr = params.maxCtr ?? 3;\n const includeBrand = params.includeBrand ?? \"all\";\n const response = await querySearchConsolePerformance(\n siteUrl,\n buildSearchConsoleQueryBody({\n startDate: params.startDate,\n endDate: params.endDate,\n dimensions: [\"query\"],\n searchType: params.searchType,\n rowLimit: params.rowLimit ?? 250,\n })\n );\n\n const opportunities = (response.rows ?? [])\n .map((row) => normalizeSearchConsoleRow(row, [\"query\"]))\n .map((row) => ({\n query: row.dimensions.query,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr_percent: row.ctr_percent,\n position: row.position,\n brand_classification: classifyBrandLabel(row.dimensions.query, params.brandTerms),\n }))\n .filter((row) => row.impressions >= minImpressions && row.ctr_percent <= maxCtr)\n .filter((row) => {\n if (includeBrand === \"only_brand\") {\n return row.brand_classification === \"brand\";\n }\n\n if (includeBrand === \"only_non_brand\") {\n return row.brand_classification === \"non_brand\";\n }\n\n return true;\n })\n .map((row) => ({\n ...row,\n opportunity_score: scoreHighImpressionLowClickOpportunity({\n impressions: row.impressions,\n ctrPercent: row.ctr_percent,\n position: row.position,\n }),\n reason: \"High impression volume with low click capture.\",\n }))\n .sort((left, right) => right.opportunity_score - left.opportunity_score);\n\n return object(\n stripNulls({\n site_url: siteUrl,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n thresholds: {\n min_impressions: minImpressions,\n max_ctr_percent: maxCtr,\n },\n opportunities,\n })\n );\n } catch (err) {\n return error(\n err instanceof Error\n ? err.message\n : \"Failed to find Search Console high-impression low-click queries\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAEpB,MAAM,mDAAmD,EAAE,OAAO;AAAA,EACvE,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc,EACX,KAAK,CAAC,OAAO,cAAc,gBAAgB,CAAC,EAC5C,SAAS,EACT,SAAS,4DAA4D;AAAA,EACxE,gBAAgB,EACb,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT,SAAS,kEAAkE;AAAA,EAC9E,QAAQ,EACL,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,SAAS,yDAAyD;AAAA,EACrE,UAAU;AACZ,CAAC;AAED,eAAsB,kDACpB,QACA;AACA,MAAI;AACF,UAAM,UAAU,4BAA4B,OAAO,SAAS,OAAO,SAAS;AAC5E,UAAM,iBAAiB,OAAO,kBAAkB;AAChD,UAAM,SAAS,OAAO,UAAU;AAChC,UAAM,eAAe,OAAO,gBAAgB;AAC5C,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,4BAA4B;AAAA,QAC1B,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,YAAY,CAAC,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,QACnB,UAAU,OAAO,YAAY;AAAA,MAC/B,CAAC;AAAA,IACH;AAEA,UAAM,iBAAiB,SAAS,QAAQ,CAAC,GACtC,IAAI,CAAC,QAAQ,0BAA0B,KAAK,CAAC,OAAO,CAAC,CAAC,EACtD,IAAI,CAAC,SAAS;AAAA,MACb,OAAO,IAAI,WAAW;AAAA,MACtB,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,sBAAsB,mBAAmB,IAAI,WAAW,OAAO,OAAO,UAAU;AAAA,IAClF,EAAE,EACD,OAAO,CAAC,QAAQ,IAAI,eAAe,kBAAkB,IAAI,eAAe,MAAM,EAC9E,OAAO,CAAC,QAAQ;AACf,UAAI,iBAAiB,cAAc;AACjC,eAAO,IAAI,yBAAyB;AAAA,MACtC;AAEA,UAAI,iBAAiB,kBAAkB;AACrC,eAAO,IAAI,yBAAyB;AAAA,MACtC;AAEA,aAAO;AAAA,IACT,CAAC,EACA,IAAI,CAAC,SAAS;AAAA,MACb,GAAG;AAAA,MACH,mBAAmB,uCAAuC;AAAA,QACxD,aAAa,IAAI;AAAA,QACjB,YAAY,IAAI;AAAA,QAChB,UAAU,IAAI;AAAA,MAChB,CAAC;AAAA,MACD,QAAQ;AAAA,IACV,EAAE,EACD,KAAK,CAAC,MAAM,UAAU,MAAM,oBAAoB,KAAK,iBAAiB;AAEzE,WAAO;AAAA,MACL,WAAW;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,UACV,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,QACnB;AAAA,QACA,YAAY;AAAA,UACV,iBAAiB;AAAA,UACjB,iBAAiB;AAAA,QACnB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QACX,IAAI,UACJ;AAAA,IACN;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./list-accessible-sites.js";
|
|
2
|
+
export * from "./site-context.js";
|
|
3
|
+
export * from "./search-performance.js";
|
|
4
|
+
export * from "./query-performance.js";
|
|
5
|
+
export * from "./page-performance.js";
|
|
6
|
+
export * from "./query-page-matrix.js";
|
|
7
|
+
export * from "./device-breakdown.js";
|
|
8
|
+
export * from "./country-breakdown.js";
|
|
9
|
+
export * from "./high-impression-low-click-queries.js";
|
|
10
|
+
export * from "./low-ctr-opportunities.js";
|
|
11
|
+
export * from "./visibility-declines.js";
|
|
12
|
+
export * from "./product-demand-low-capture-queries.js";
|
|
13
|
+
export * from "./rising-non-brand-queries.js";
|
|
14
|
+
export * from "./quick-win-opportunities.js";
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./list-accessible-sites.js\";\nexport * from \"./site-context.js\";\nexport * from \"./search-performance.js\";\nexport * from \"./query-performance.js\";\nexport * from \"./page-performance.js\";\nexport * from \"./query-page-matrix.js\";\nexport * from \"./device-breakdown.js\";\nexport * from \"./country-breakdown.js\";\nexport * from \"./high-impression-low-click-queries.js\";\nexport * from \"./low-ctr-opportunities.js\";\nexport * from \"./visibility-declines.js\";\nexport * from \"./product-demand-low-capture-queries.js\";\nexport * from \"./rising-non-brand-queries.js\";\nexport * from \"./quick-win-opportunities.js\";\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { listSearchConsoleSites } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
normalizePermissionLevel,
|
|
6
|
+
normalizeSearchConsoleSiteEntry
|
|
7
|
+
} from "../../search-console/search-console-utils.js";
|
|
8
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
9
|
+
const listAccessibleSitesSchema = z.object({});
|
|
10
|
+
async function listAccessibleSitesHandler(_params) {
|
|
11
|
+
try {
|
|
12
|
+
const sites = await listSearchConsoleSites();
|
|
13
|
+
const normalizedSites = sites.map(normalizeSearchConsoleSiteEntry).sort((left, right) => left.site_url.localeCompare(right.site_url));
|
|
14
|
+
const preferredDomainProperty = normalizedSites.find(
|
|
15
|
+
(site) => site.property_type === "domain" && (site.permission_level === "siteowner" || site.permission_level === "owner")
|
|
16
|
+
);
|
|
17
|
+
return object(
|
|
18
|
+
stripNulls({
|
|
19
|
+
total_sites: normalizedSites.length,
|
|
20
|
+
recommended_default_site_url: preferredDomainProperty?.site_url ?? normalizedSites[0]?.site_url,
|
|
21
|
+
sites: normalizedSites,
|
|
22
|
+
summary: {
|
|
23
|
+
domain_properties: normalizedSites.filter((site) => site.property_type === "domain").length,
|
|
24
|
+
url_prefix_properties: normalizedSites.filter((site) => site.property_type === "url_prefix").length,
|
|
25
|
+
owner_level_sites: sites.filter((site) => {
|
|
26
|
+
const permissionLevel = normalizePermissionLevel(site.permissionLevel);
|
|
27
|
+
return permissionLevel === "owner" || permissionLevel === "siteowner";
|
|
28
|
+
}).length
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return error(
|
|
34
|
+
err instanceof Error ? err.message : "Failed to list accessible Search Console sites"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
listAccessibleSitesHandler,
|
|
40
|
+
listAccessibleSitesSchema
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=list-accessible-sites.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/list-accessible-sites.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { listSearchConsoleSites } from \"../../services/search-console/search-console-client.js\";\nimport {\n inferSearchConsolePropertyType,\n normalizePermissionLevel,\n normalizeSearchConsoleSiteEntry,\n} from \"../../search-console/search-console-utils.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const listAccessibleSitesSchema = z.object({});\n\nexport async function listAccessibleSitesHandler(\n _params: z.infer<typeof listAccessibleSitesSchema>\n) {\n try {\n const sites = await listSearchConsoleSites();\n const normalizedSites = sites\n .map(normalizeSearchConsoleSiteEntry)\n .sort((left, right) => left.site_url.localeCompare(right.site_url));\n\n const preferredDomainProperty = normalizedSites.find(\n (site) =>\n site.property_type === \"domain\" &&\n (site.permission_level === \"siteowner\" || site.permission_level === \"owner\")\n );\n\n return object(\n stripNulls({\n total_sites: normalizedSites.length,\n recommended_default_site_url: preferredDomainProperty?.site_url ?? normalizedSites[0]?.site_url,\n sites: normalizedSites,\n summary: {\n domain_properties: normalizedSites.filter((site) => site.property_type === \"domain\").length,\n url_prefix_properties: normalizedSites.filter((site) => site.property_type === \"url_prefix\").length,\n owner_level_sites: sites.filter((site) => {\n const permissionLevel = normalizePermissionLevel(site.permissionLevel);\n return permissionLevel === \"owner\" || permissionLevel === \"siteowner\";\n }).length,\n },\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to list accessible Search Console sites\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAEpB,MAAM,4BAA4B,EAAE,OAAO,CAAC,CAAC;AAEpD,eAAsB,2BACpB,SACA;AACA,MAAI;AACF,UAAM,QAAQ,MAAM,uBAAuB;AAC3C,UAAM,kBAAkB,MACrB,IAAI,+BAA+B,EACnC,KAAK,CAAC,MAAM,UAAU,KAAK,SAAS,cAAc,MAAM,QAAQ,CAAC;AAEpE,UAAM,0BAA0B,gBAAgB;AAAA,MAC9C,CAAC,SACC,KAAK,kBAAkB,aACtB,KAAK,qBAAqB,eAAe,KAAK,qBAAqB;AAAA,IACxE;AAEA,WAAO;AAAA,MACL,WAAW;AAAA,QACT,aAAa,gBAAgB;AAAA,QAC7B,8BAA8B,yBAAyB,YAAY,gBAAgB,CAAC,GAAG;AAAA,QACvF,OAAO;AAAA,QACP,SAAS;AAAA,UACP,mBAAmB,gBAAgB,OAAO,CAAC,SAAS,KAAK,kBAAkB,QAAQ,EAAE;AAAA,UACrF,uBAAuB,gBAAgB,OAAO,CAAC,SAAS,KAAK,kBAAkB,YAAY,EAAE;AAAA,UAC7F,mBAAmB,MAAM,OAAO,CAAC,SAAS;AACxC,kBAAM,kBAAkB,yBAAyB,KAAK,eAAe;AACrE,mBAAO,oBAAoB,WAAW,oBAAoB;AAAA,UAC5D,CAAC,EAAE;AAAA,QACL;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { querySearchConsolePerformance } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
brandTermsSchemaField,
|
|
6
|
+
buildSearchConsoleQueryBody,
|
|
7
|
+
classifyBrandLabel,
|
|
8
|
+
endDateSchemaField,
|
|
9
|
+
profileIdSchemaField,
|
|
10
|
+
resolveSearchConsoleSiteUrl,
|
|
11
|
+
scoreQuickWinOpportunity,
|
|
12
|
+
searchTypeSchemaField,
|
|
13
|
+
siteUrlSchemaField,
|
|
14
|
+
startDateSchemaField,
|
|
15
|
+
normalizeSearchConsoleRow
|
|
16
|
+
} from "../../search-console/search-console-utils.js";
|
|
17
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
18
|
+
const searchConsoleLowCtrOpportunitiesSchema = z.object({
|
|
19
|
+
startDate: startDateSchemaField,
|
|
20
|
+
endDate: endDateSchemaField,
|
|
21
|
+
siteUrl: siteUrlSchemaField,
|
|
22
|
+
profileId: profileIdSchemaField,
|
|
23
|
+
searchType: searchTypeSchemaField,
|
|
24
|
+
brandTerms: brandTermsSchemaField,
|
|
25
|
+
includeBrand: z.enum(["all", "only_brand", "only_non_brand"]).optional().describe("How to filter the query list when brandTerms are provided."),
|
|
26
|
+
minImpressions: z.number().min(1).optional().describe("Minimum impressions required to consider a CTR opportunity."),
|
|
27
|
+
maxCtr: z.number().min(0).max(100).optional().describe("Maximum CTR percentage allowed to count as underperforming."),
|
|
28
|
+
maxPosition: z.number().min(1).max(100).optional().describe("Maximum average position to keep only queries that already rank reasonably well.")
|
|
29
|
+
});
|
|
30
|
+
async function searchConsoleLowCtrOpportunitiesHandler(params) {
|
|
31
|
+
try {
|
|
32
|
+
const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);
|
|
33
|
+
const minImpressions = params.minImpressions ?? 100;
|
|
34
|
+
const maxCtr = params.maxCtr ?? 4;
|
|
35
|
+
const maxPosition = params.maxPosition ?? 10;
|
|
36
|
+
const includeBrand = params.includeBrand ?? "all";
|
|
37
|
+
const response = await querySearchConsolePerformance(
|
|
38
|
+
siteUrl,
|
|
39
|
+
buildSearchConsoleQueryBody({
|
|
40
|
+
startDate: params.startDate,
|
|
41
|
+
endDate: params.endDate,
|
|
42
|
+
dimensions: ["query"],
|
|
43
|
+
searchType: params.searchType,
|
|
44
|
+
rowLimit: 250
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
const opportunities = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["query"])).map((row) => ({
|
|
48
|
+
query: row.dimensions.query,
|
|
49
|
+
clicks: row.clicks,
|
|
50
|
+
impressions: row.impressions,
|
|
51
|
+
ctr_percent: row.ctr_percent,
|
|
52
|
+
position: row.position,
|
|
53
|
+
brand_classification: classifyBrandLabel(row.dimensions.query, params.brandTerms)
|
|
54
|
+
})).filter(
|
|
55
|
+
(row) => row.impressions >= minImpressions && row.ctr_percent <= maxCtr && row.position > 0 && row.position <= maxPosition
|
|
56
|
+
).filter((row) => {
|
|
57
|
+
if (includeBrand === "only_brand") {
|
|
58
|
+
return row.brand_classification === "brand";
|
|
59
|
+
}
|
|
60
|
+
if (includeBrand === "only_non_brand") {
|
|
61
|
+
return row.brand_classification === "non_brand";
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}).map((row) => ({
|
|
65
|
+
...row,
|
|
66
|
+
opportunity_score: scoreQuickWinOpportunity({
|
|
67
|
+
impressions: row.impressions,
|
|
68
|
+
ctrPercent: row.ctr_percent,
|
|
69
|
+
position: row.position
|
|
70
|
+
}),
|
|
71
|
+
reason: "Ranking is already acceptable, so low CTR suggests snippet or title optimization potential."
|
|
72
|
+
})).sort((left, right) => right.opportunity_score - left.opportunity_score);
|
|
73
|
+
return object(
|
|
74
|
+
stripNulls({
|
|
75
|
+
site_url: siteUrl,
|
|
76
|
+
date_range: {
|
|
77
|
+
start_date: params.startDate,
|
|
78
|
+
end_date: params.endDate
|
|
79
|
+
},
|
|
80
|
+
thresholds: {
|
|
81
|
+
min_impressions: minImpressions,
|
|
82
|
+
max_ctr_percent: maxCtr,
|
|
83
|
+
max_position: maxPosition
|
|
84
|
+
},
|
|
85
|
+
opportunities
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
return error(
|
|
90
|
+
err instanceof Error ? err.message : "Failed to find Search Console low-CTR opportunities"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
searchConsoleLowCtrOpportunitiesHandler,
|
|
96
|
+
searchConsoleLowCtrOpportunitiesSchema
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=low-ctr-opportunities.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/low-ctr-opportunities.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { querySearchConsolePerformance } from \"../../services/search-console/search-console-client.js\";\nimport {\n brandTermsSchemaField,\n buildSearchConsoleQueryBody,\n classifyBrandLabel,\n endDateSchemaField,\n profileIdSchemaField,\n resolveSearchConsoleSiteUrl,\n scoreQuickWinOpportunity,\n searchTypeSchemaField,\n siteUrlSchemaField,\n startDateSchemaField,\n normalizeSearchConsoleRow,\n} from \"../../search-console/search-console-utils.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const searchConsoleLowCtrOpportunitiesSchema = z.object({\n startDate: startDateSchemaField,\n endDate: endDateSchemaField,\n siteUrl: siteUrlSchemaField,\n profileId: profileIdSchemaField,\n searchType: searchTypeSchemaField,\n brandTerms: brandTermsSchemaField,\n includeBrand: z\n .enum([\"all\", \"only_brand\", \"only_non_brand\"])\n .optional()\n .describe(\"How to filter the query list when brandTerms are provided.\"),\n minImpressions: z\n .number()\n .min(1)\n .optional()\n .describe(\"Minimum impressions required to consider a CTR opportunity.\"),\n maxCtr: z\n .number()\n .min(0)\n .max(100)\n .optional()\n .describe(\"Maximum CTR percentage allowed to count as underperforming.\"),\n maxPosition: z\n .number()\n .min(1)\n .max(100)\n .optional()\n .describe(\"Maximum average position to keep only queries that already rank reasonably well.\"),\n});\n\nexport async function searchConsoleLowCtrOpportunitiesHandler(\n params: z.infer<typeof searchConsoleLowCtrOpportunitiesSchema>\n) {\n try {\n const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);\n const minImpressions = params.minImpressions ?? 100;\n const maxCtr = params.maxCtr ?? 4;\n const maxPosition = params.maxPosition ?? 10;\n const includeBrand = params.includeBrand ?? \"all\";\n\n const response = await querySearchConsolePerformance(\n siteUrl,\n buildSearchConsoleQueryBody({\n startDate: params.startDate,\n endDate: params.endDate,\n dimensions: [\"query\"],\n searchType: params.searchType,\n rowLimit: 250,\n })\n );\n\n const opportunities = (response.rows ?? [])\n .map((row) => normalizeSearchConsoleRow(row, [\"query\"]))\n .map((row) => ({\n query: row.dimensions.query,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr_percent: row.ctr_percent,\n position: row.position,\n brand_classification: classifyBrandLabel(row.dimensions.query, params.brandTerms),\n }))\n .filter(\n (row) =>\n row.impressions >= minImpressions &&\n row.ctr_percent <= maxCtr &&\n row.position > 0 &&\n row.position <= maxPosition\n )\n .filter((row) => {\n if (includeBrand === \"only_brand\") {\n return row.brand_classification === \"brand\";\n }\n\n if (includeBrand === \"only_non_brand\") {\n return row.brand_classification === \"non_brand\";\n }\n\n return true;\n })\n .map((row) => ({\n ...row,\n opportunity_score: scoreQuickWinOpportunity({\n impressions: row.impressions,\n ctrPercent: row.ctr_percent,\n position: row.position,\n }),\n reason: \"Ranking is already acceptable, so low CTR suggests snippet or title optimization potential.\",\n }))\n .sort((left, right) => right.opportunity_score - left.opportunity_score);\n\n return object(\n stripNulls({\n site_url: siteUrl,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n thresholds: {\n min_impressions: minImpressions,\n max_ctr_percent: maxCtr,\n max_position: maxPosition,\n },\n opportunities,\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to find Search Console low-CTR opportunities\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAEpB,MAAM,yCAAyC,EAAE,OAAO;AAAA,EAC7D,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc,EACX,KAAK,CAAC,OAAO,cAAc,gBAAgB,CAAC,EAC5C,SAAS,EACT,SAAS,4DAA4D;AAAA,EACxE,gBAAgB,EACb,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT,SAAS,6DAA6D;AAAA,EACzE,QAAQ,EACL,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,SAAS,6DAA6D;AAAA,EACzE,aAAa,EACV,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,SAAS,kFAAkF;AAChG,CAAC;AAED,eAAsB,wCACpB,QACA;AACA,MAAI;AACF,UAAM,UAAU,4BAA4B,OAAO,SAAS,OAAO,SAAS;AAC5E,UAAM,iBAAiB,OAAO,kBAAkB;AAChD,UAAM,SAAS,OAAO,UAAU;AAChC,UAAM,cAAc,OAAO,eAAe;AAC1C,UAAM,eAAe,OAAO,gBAAgB;AAE5C,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,4BAA4B;AAAA,QAC1B,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,YAAY,CAAC,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,QACnB,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAEA,UAAM,iBAAiB,SAAS,QAAQ,CAAC,GACtC,IAAI,CAAC,QAAQ,0BAA0B,KAAK,CAAC,OAAO,CAAC,CAAC,EACtD,IAAI,CAAC,SAAS;AAAA,MACb,OAAO,IAAI,WAAW;AAAA,MACtB,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,sBAAsB,mBAAmB,IAAI,WAAW,OAAO,OAAO,UAAU;AAAA,IAClF,EAAE,EACD;AAAA,MACC,CAAC,QACC,IAAI,eAAe,kBACnB,IAAI,eAAe,UACnB,IAAI,WAAW,KACf,IAAI,YAAY;AAAA,IACpB,EACC,OAAO,CAAC,QAAQ;AACf,UAAI,iBAAiB,cAAc;AACjC,eAAO,IAAI,yBAAyB;AAAA,MACtC;AAEA,UAAI,iBAAiB,kBAAkB;AACrC,eAAO,IAAI,yBAAyB;AAAA,MACtC;AAEA,aAAO;AAAA,IACT,CAAC,EACA,IAAI,CAAC,SAAS;AAAA,MACb,GAAG;AAAA,MACH,mBAAmB,yBAAyB;AAAA,QAC1C,aAAa,IAAI;AAAA,QACjB,YAAY,IAAI;AAAA,QAChB,UAAU,IAAI;AAAA,MAChB,CAAC;AAAA,MACD,QAAQ;AAAA,IACV,EAAE,EACD,KAAK,CAAC,MAAM,UAAU,MAAM,oBAAoB,KAAK,iBAAiB;AAEzE,WAAO;AAAA,MACL,WAAW;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,UACV,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,QACnB;AAAA,QACA,YAAY;AAAA,UACV,iBAAiB;AAAA,UACjB,iBAAiB;AAAA,UACjB,cAAc;AAAA,QAChB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { querySearchConsolePerformance } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
buildPaginationMetadata,
|
|
6
|
+
buildSearchConsoleQueryBody,
|
|
7
|
+
endDateSchemaField,
|
|
8
|
+
profileIdSchemaField,
|
|
9
|
+
resolveSearchConsoleSiteUrl,
|
|
10
|
+
rowLimitSchemaField,
|
|
11
|
+
searchTypeSchemaField,
|
|
12
|
+
siteUrlSchemaField,
|
|
13
|
+
startDateSchemaField,
|
|
14
|
+
startRowSchemaField,
|
|
15
|
+
sumSearchConsoleRows,
|
|
16
|
+
normalizeSearchConsoleRow
|
|
17
|
+
} from "../../search-console/search-console-utils.js";
|
|
18
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
19
|
+
const searchConsolePagePerformanceSchema = z.object({
|
|
20
|
+
startDate: startDateSchemaField,
|
|
21
|
+
endDate: endDateSchemaField,
|
|
22
|
+
siteUrl: siteUrlSchemaField,
|
|
23
|
+
profileId: profileIdSchemaField,
|
|
24
|
+
searchType: searchTypeSchemaField,
|
|
25
|
+
pageContains: z.string().optional().describe("Optional substring that page URLs must contain."),
|
|
26
|
+
pageRegex: z.string().optional().describe("Optional regex expression to match page URLs."),
|
|
27
|
+
rowLimit: rowLimitSchemaField,
|
|
28
|
+
startRow: startRowSchemaField
|
|
29
|
+
});
|
|
30
|
+
async function searchConsolePagePerformanceHandler(params) {
|
|
31
|
+
try {
|
|
32
|
+
const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);
|
|
33
|
+
const dimensionFilters = [];
|
|
34
|
+
if (params.pageContains?.trim()) {
|
|
35
|
+
dimensionFilters.push({
|
|
36
|
+
dimension: "page",
|
|
37
|
+
operator: "contains",
|
|
38
|
+
expression: params.pageContains.trim()
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (params.pageRegex?.trim()) {
|
|
42
|
+
dimensionFilters.push({
|
|
43
|
+
dimension: "page",
|
|
44
|
+
operator: "includingRegex",
|
|
45
|
+
expression: params.pageRegex.trim()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const response = await querySearchConsolePerformance(
|
|
49
|
+
siteUrl,
|
|
50
|
+
buildSearchConsoleQueryBody({
|
|
51
|
+
startDate: params.startDate,
|
|
52
|
+
endDate: params.endDate,
|
|
53
|
+
dimensions: ["page"],
|
|
54
|
+
searchType: params.searchType,
|
|
55
|
+
dimensionFilters,
|
|
56
|
+
rowLimit: params.rowLimit,
|
|
57
|
+
startRow: params.startRow
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["page"]));
|
|
61
|
+
const totals = sumSearchConsoleRows(rows);
|
|
62
|
+
return object(
|
|
63
|
+
stripNulls({
|
|
64
|
+
site_url: siteUrl,
|
|
65
|
+
date_range: {
|
|
66
|
+
start_date: params.startDate,
|
|
67
|
+
end_date: params.endDate
|
|
68
|
+
},
|
|
69
|
+
filters: {
|
|
70
|
+
search_type: params.searchType ?? "web",
|
|
71
|
+
page_contains: params.pageContains,
|
|
72
|
+
page_regex: params.pageRegex
|
|
73
|
+
},
|
|
74
|
+
overview: {
|
|
75
|
+
total_clicks: totals.clicks,
|
|
76
|
+
total_impressions: totals.impressions,
|
|
77
|
+
average_ctr_percent: totals.ctrPercent,
|
|
78
|
+
average_position: totals.averagePosition
|
|
79
|
+
},
|
|
80
|
+
pages: rows.map((row) => ({
|
|
81
|
+
page: row.dimensions.page,
|
|
82
|
+
clicks: row.clicks,
|
|
83
|
+
impressions: row.impressions,
|
|
84
|
+
ctr_percent: row.ctr_percent,
|
|
85
|
+
position: row.position
|
|
86
|
+
})),
|
|
87
|
+
metadata: buildPaginationMetadata({
|
|
88
|
+
startRow: params.startRow,
|
|
89
|
+
rowLimit: params.rowLimit,
|
|
90
|
+
returnedRows: rows.length
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return error(
|
|
96
|
+
err instanceof Error ? err.message : "Failed to fetch Search Console page performance"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
searchConsolePagePerformanceHandler,
|
|
102
|
+
searchConsolePagePerformanceSchema
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=page-performance.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/page-performance.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { querySearchConsolePerformance } from \"../../services/search-console/search-console-client.js\";\nimport {\n buildPaginationMetadata,\n buildSearchConsoleQueryBody,\n endDateSchemaField,\n profileIdSchemaField,\n resolveSearchConsoleSiteUrl,\n rowLimitSchemaField,\n searchConsoleDimensionFilterSchema,\n searchTypeSchemaField,\n siteUrlSchemaField,\n startDateSchemaField,\n startRowSchemaField,\n sumSearchConsoleRows,\n normalizeSearchConsoleRow,\n} from \"../../search-console/search-console-utils.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const searchConsolePagePerformanceSchema = z.object({\n startDate: startDateSchemaField,\n endDate: endDateSchemaField,\n siteUrl: siteUrlSchemaField,\n profileId: profileIdSchemaField,\n searchType: searchTypeSchemaField,\n pageContains: z\n .string()\n .optional()\n .describe(\"Optional substring that page URLs must contain.\"),\n pageRegex: z\n .string()\n .optional()\n .describe(\"Optional regex expression to match page URLs.\"),\n rowLimit: rowLimitSchemaField,\n startRow: startRowSchemaField,\n});\n\nexport async function searchConsolePagePerformanceHandler(\n params: z.infer<typeof searchConsolePagePerformanceSchema>\n) {\n try {\n const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);\n const dimensionFilters = [];\n\n if (params.pageContains?.trim()) {\n dimensionFilters.push({\n dimension: \"page\",\n operator: \"contains\",\n expression: params.pageContains.trim(),\n });\n }\n\n if (params.pageRegex?.trim()) {\n dimensionFilters.push({\n dimension: \"page\",\n operator: \"includingRegex\",\n expression: params.pageRegex.trim(),\n });\n }\n\n const response = await querySearchConsolePerformance(\n siteUrl,\n buildSearchConsoleQueryBody({\n startDate: params.startDate,\n endDate: params.endDate,\n dimensions: [\"page\"],\n searchType: params.searchType,\n dimensionFilters,\n rowLimit: params.rowLimit,\n startRow: params.startRow,\n })\n );\n\n const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, [\"page\"]));\n const totals = sumSearchConsoleRows(rows);\n\n return object(\n stripNulls({\n site_url: siteUrl,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n filters: {\n search_type: params.searchType ?? \"web\",\n page_contains: params.pageContains,\n page_regex: params.pageRegex,\n },\n overview: {\n total_clicks: totals.clicks,\n total_impressions: totals.impressions,\n average_ctr_percent: totals.ctrPercent,\n average_position: totals.averagePosition,\n },\n pages: rows.map((row) => ({\n page: row.dimensions.page,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr_percent: row.ctr_percent,\n position: row.position,\n })),\n metadata: buildPaginationMetadata({\n startRow: params.startRow,\n rowLimit: params.rowLimit,\n returnedRows: rows.length,\n }),\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to fetch Search Console page performance\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAEpB,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,cAAc,EACX,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,EAC7D,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,+CAA+C;AAAA,EAC3D,UAAU;AAAA,EACV,UAAU;AACZ,CAAC;AAED,eAAsB,oCACpB,QACA;AACA,MAAI;AACF,UAAM,UAAU,4BAA4B,OAAO,SAAS,OAAO,SAAS;AAC5E,UAAM,mBAAmB,CAAC;AAE1B,QAAI,OAAO,cAAc,KAAK,GAAG;AAC/B,uBAAiB,KAAK;AAAA,QACpB,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY,OAAO,aAAa,KAAK;AAAA,MACvC,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,WAAW,KAAK,GAAG;AAC5B,uBAAiB,KAAK;AAAA,QACpB,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY,OAAO,UAAU,KAAK;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,4BAA4B;AAAA,QAC1B,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,YAAY,CAAC,MAAM;AAAA,QACnB,YAAY,OAAO;AAAA,QACnB;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,0BAA0B,KAAK,CAAC,MAAM,CAAC,CAAC;AACxF,UAAM,SAAS,qBAAqB,IAAI;AAExC,WAAO;AAAA,MACL,WAAW;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,UACV,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,QACnB;AAAA,QACA,SAAS;AAAA,UACP,aAAa,OAAO,cAAc;AAAA,UAClC,eAAe,OAAO;AAAA,UACtB,YAAY,OAAO;AAAA,QACrB;AAAA,QACA,UAAU;AAAA,UACR,cAAc,OAAO;AAAA,UACrB,mBAAmB,OAAO;AAAA,UAC1B,qBAAqB,OAAO;AAAA,UAC5B,kBAAkB,OAAO;AAAA,QAC3B;AAAA,QACA,OAAO,KAAK,IAAI,CAAC,SAAS;AAAA,UACxB,MAAM,IAAI,WAAW;AAAA,UACrB,QAAQ,IAAI;AAAA,UACZ,aAAa,IAAI;AAAA,UACjB,aAAa,IAAI;AAAA,UACjB,UAAU,IAAI;AAAA,QAChB,EAAE;AAAA,QACF,UAAU,wBAAwB;AAAA,UAChC,UAAU,OAAO;AAAA,UACjB,UAAU,OAAO;AAAA,UACjB,cAAc,KAAK;AAAA,QACrB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { querySearchConsolePerformance } from "../../services/search-console/search-console-client.js";
|
|
4
|
+
import {
|
|
5
|
+
brandTermsSchemaField,
|
|
6
|
+
buildSearchConsoleQueryBody,
|
|
7
|
+
classifyBrandLabel,
|
|
8
|
+
endDateSchemaField,
|
|
9
|
+
looksLikeProductQuery,
|
|
10
|
+
profileIdSchemaField,
|
|
11
|
+
resolveSearchConsoleSiteUrl,
|
|
12
|
+
rowLimitSchemaField,
|
|
13
|
+
searchTypeSchemaField,
|
|
14
|
+
siteUrlSchemaField,
|
|
15
|
+
startDateSchemaField,
|
|
16
|
+
normalizeSearchConsoleRow,
|
|
17
|
+
scoreHighImpressionLowClickOpportunity
|
|
18
|
+
} from "../../search-console/search-console-utils.js";
|
|
19
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
20
|
+
const searchConsoleProductDemandLowCaptureQueriesSchema = z.object({
|
|
21
|
+
startDate: startDateSchemaField,
|
|
22
|
+
endDate: endDateSchemaField,
|
|
23
|
+
siteUrl: siteUrlSchemaField,
|
|
24
|
+
profileId: profileIdSchemaField,
|
|
25
|
+
searchType: searchTypeSchemaField,
|
|
26
|
+
brandTerms: brandTermsSchemaField,
|
|
27
|
+
productTerms: z.array(z.string().min(1)).max(100).optional().describe("Optional product tokens used to better detect product-intent queries."),
|
|
28
|
+
minImpressions: z.number().min(1).optional().describe("Minimum impressions required to consider a product query."),
|
|
29
|
+
maxCtr: z.number().min(0).max(100).optional().describe("Maximum CTR percentage allowed to count as low capture."),
|
|
30
|
+
maxPosition: z.number().min(1).max(100).optional().describe("Maximum average position allowed to keep only queries with some ranking potential."),
|
|
31
|
+
rowLimit: rowLimitSchemaField
|
|
32
|
+
});
|
|
33
|
+
async function searchConsoleProductDemandLowCaptureQueriesHandler(params) {
|
|
34
|
+
try {
|
|
35
|
+
const siteUrl = resolveSearchConsoleSiteUrl(params.siteUrl, params.profileId);
|
|
36
|
+
const minImpressions = params.minImpressions ?? 80;
|
|
37
|
+
const maxCtr = params.maxCtr ?? 4;
|
|
38
|
+
const maxPosition = params.maxPosition ?? 20;
|
|
39
|
+
const response = await querySearchConsolePerformance(
|
|
40
|
+
siteUrl,
|
|
41
|
+
buildSearchConsoleQueryBody({
|
|
42
|
+
startDate: params.startDate,
|
|
43
|
+
endDate: params.endDate,
|
|
44
|
+
dimensions: ["query", "page"],
|
|
45
|
+
searchType: params.searchType,
|
|
46
|
+
rowLimit: params.rowLimit ?? 250
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
const opportunities = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["query", "page"])).filter((row) => looksLikeProductQuery(row.dimensions.query, params.productTerms)).map((row) => ({
|
|
50
|
+
query: row.dimensions.query,
|
|
51
|
+
page: row.dimensions.page,
|
|
52
|
+
clicks: row.clicks,
|
|
53
|
+
impressions: row.impressions,
|
|
54
|
+
ctr_percent: row.ctr_percent,
|
|
55
|
+
position: row.position,
|
|
56
|
+
brand_classification: classifyBrandLabel(row.dimensions.query, params.brandTerms)
|
|
57
|
+
})).filter(
|
|
58
|
+
(row) => row.impressions >= minImpressions && row.ctr_percent <= maxCtr && row.position > 0 && row.position <= maxPosition
|
|
59
|
+
).map((row) => ({
|
|
60
|
+
...row,
|
|
61
|
+
opportunity_score: scoreHighImpressionLowClickOpportunity({
|
|
62
|
+
impressions: row.impressions,
|
|
63
|
+
ctrPercent: row.ctr_percent,
|
|
64
|
+
position: row.position
|
|
65
|
+
}),
|
|
66
|
+
reason: "Product-intent query shows demand but weak click capture."
|
|
67
|
+
})).sort((left, right) => right.opportunity_score - left.opportunity_score);
|
|
68
|
+
return object(
|
|
69
|
+
stripNulls({
|
|
70
|
+
site_url: siteUrl,
|
|
71
|
+
date_range: {
|
|
72
|
+
start_date: params.startDate,
|
|
73
|
+
end_date: params.endDate
|
|
74
|
+
},
|
|
75
|
+
thresholds: {
|
|
76
|
+
min_impressions: minImpressions,
|
|
77
|
+
max_ctr_percent: maxCtr,
|
|
78
|
+
max_position: maxPosition
|
|
79
|
+
},
|
|
80
|
+
opportunities
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return error(
|
|
85
|
+
err instanceof Error ? err.message : "Failed to find Search Console product demand low-capture queries"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
searchConsoleProductDemandLowCaptureQueriesHandler,
|
|
91
|
+
searchConsoleProductDemandLowCaptureQueriesSchema
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=product-demand-low-capture-queries.js.map
|