@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.
Files changed (165) hide show
  1. package/README.md +346 -0
  2. package/bin/bi-mcp.js +2 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/index.js +768 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/mcp-use.json +7 -0
  7. package/dist/public/favicon.ico +0 -0
  8. package/dist/public/icon.svg +6 -0
  9. package/dist/src/analytics/ga4-channel-groups.js +20 -0
  10. package/dist/src/analytics/ga4-channel-groups.js.map +7 -0
  11. package/dist/src/analytics/ga4-report-utils.js +117 -0
  12. package/dist/src/analytics/ga4-report-utils.js.map +7 -0
  13. package/dist/src/config/benchmarks.js +128 -0
  14. package/dist/src/config/benchmarks.js.map +7 -0
  15. package/dist/src/config/google.js +41 -0
  16. package/dist/src/config/google.js.map +7 -0
  17. package/dist/src/config/vtex.js +26 -0
  18. package/dist/src/config/vtex.js.map +7 -0
  19. package/dist/src/google-ads/report-utils.js +78 -0
  20. package/dist/src/google-ads/report-utils.js.map +7 -0
  21. package/dist/src/prompts/reporte-ventas.js +75 -0
  22. package/dist/src/prompts/reporte-ventas.js.map +7 -0
  23. package/dist/src/search-console/search-console-utils.js +275 -0
  24. package/dist/src/search-console/search-console-utils.js.map +7 -0
  25. package/dist/src/services/analytics/ga4-client.js +69 -0
  26. package/dist/src/services/analytics/ga4-client.js.map +7 -0
  27. package/dist/src/services/analytics/oauth.js +30 -0
  28. package/dist/src/services/analytics/oauth.js.map +7 -0
  29. package/dist/src/services/google-ads/google-ads-client.js +54 -0
  30. package/dist/src/services/google-ads/google-ads-client.js.map +7 -0
  31. package/dist/src/services/search-console/search-console-client.js +45 -0
  32. package/dist/src/services/search-console/search-console-client.js.map +7 -0
  33. package/dist/src/services/vtex/vtex-api.js +51 -0
  34. package/dist/src/services/vtex/vtex-api.js.map +7 -0
  35. package/dist/src/services/vtex/vtex-catalog.js +18 -0
  36. package/dist/src/services/vtex/vtex-catalog.js.map +7 -0
  37. package/dist/src/services/vtex/vtex-logistics.js +151 -0
  38. package/dist/src/services/vtex/vtex-logistics.js.map +7 -0
  39. package/dist/src/services/vtex/vtex-orders.js +143 -0
  40. package/dist/src/services/vtex/vtex-orders.js.map +7 -0
  41. package/dist/src/services/vtex/vtex-pricing.js +17 -0
  42. package/dist/src/services/vtex/vtex-pricing.js.map +7 -0
  43. package/dist/src/tools/analytics/attribution-gaps.js +109 -0
  44. package/dist/src/tools/analytics/attribution-gaps.js.map +7 -0
  45. package/dist/src/tools/analytics/channel-mix.js +74 -0
  46. package/dist/src/tools/analytics/channel-mix.js.map +7 -0
  47. package/dist/src/tools/analytics/ecommerce-tracking-health.js +89 -0
  48. package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +7 -0
  49. package/dist/src/tools/analytics/engagement-overview.js +71 -0
  50. package/dist/src/tools/analytics/engagement-overview.js.map +7 -0
  51. package/dist/src/tools/analytics/index.js +12 -0
  52. package/dist/src/tools/analytics/index.js.map +7 -0
  53. package/dist/src/tools/analytics/list-accessible-properties.js +46 -0
  54. package/dist/src/tools/analytics/list-accessible-properties.js.map +7 -0
  55. package/dist/src/tools/analytics/property-info.js +54 -0
  56. package/dist/src/tools/analytics/property-info.js.map +7 -0
  57. package/dist/src/tools/analytics/revenue-by-channel.js +70 -0
  58. package/dist/src/tools/analytics/revenue-by-channel.js.map +7 -0
  59. package/dist/src/tools/analytics/revenue-overview.js +77 -0
  60. package/dist/src/tools/analytics/revenue-overview.js.map +7 -0
  61. package/dist/src/tools/analytics/revenue-trend.js +69 -0
  62. package/dist/src/tools/analytics/revenue-trend.js.map +7 -0
  63. package/dist/src/tools/analytics/source-medium-breakdown.js +86 -0
  64. package/dist/src/tools/analytics/source-medium-breakdown.js.map +7 -0
  65. package/dist/src/tools/analytics/top-landing-pages.js +79 -0
  66. package/dist/src/tools/analytics/top-landing-pages.js.map +7 -0
  67. package/dist/src/tools/google-ads/account-overview.js +103 -0
  68. package/dist/src/tools/google-ads/account-overview.js.map +7 -0
  69. package/dist/src/tools/google-ads/account-risks.js +267 -0
  70. package/dist/src/tools/google-ads/account-risks.js.map +7 -0
  71. package/dist/src/tools/google-ads/break-even-analysis.js +107 -0
  72. package/dist/src/tools/google-ads/break-even-analysis.js.map +7 -0
  73. package/dist/src/tools/google-ads/campaign-performance.js +157 -0
  74. package/dist/src/tools/google-ads/campaign-performance.js.map +7 -0
  75. package/dist/src/tools/google-ads/channel-mix.js +129 -0
  76. package/dist/src/tools/google-ads/channel-mix.js.map +7 -0
  77. package/dist/src/tools/google-ads/compare-accounts.js +122 -0
  78. package/dist/src/tools/google-ads/compare-accounts.js.map +7 -0
  79. package/dist/src/tools/google-ads/customer-clients.js +77 -0
  80. package/dist/src/tools/google-ads/customer-clients.js.map +7 -0
  81. package/dist/src/tools/google-ads/customer-info.js +64 -0
  82. package/dist/src/tools/google-ads/customer-info.js.map +7 -0
  83. package/dist/src/tools/google-ads/index.js +12 -0
  84. package/dist/src/tools/google-ads/index.js.map +7 -0
  85. package/dist/src/tools/google-ads/scaling-health.js +174 -0
  86. package/dist/src/tools/google-ads/scaling-health.js.map +7 -0
  87. package/dist/src/tools/google-ads/search-terms-summary.js +131 -0
  88. package/dist/src/tools/google-ads/search-terms-summary.js.map +7 -0
  89. package/dist/src/tools/google-ads/time-series.js +126 -0
  90. package/dist/src/tools/google-ads/time-series.js.map +7 -0
  91. package/dist/src/tools/index.js +5 -0
  92. package/dist/src/tools/index.js.map +7 -0
  93. package/dist/src/tools/search-console/country-breakdown.js +85 -0
  94. package/dist/src/tools/search-console/country-breakdown.js.map +7 -0
  95. package/dist/src/tools/search-console/device-breakdown.js +85 -0
  96. package/dist/src/tools/search-console/device-breakdown.js.map +7 -0
  97. package/dist/src/tools/search-console/high-impression-low-click-queries.js +95 -0
  98. package/dist/src/tools/search-console/high-impression-low-click-queries.js.map +7 -0
  99. package/dist/src/tools/search-console/index.js +15 -0
  100. package/dist/src/tools/search-console/index.js.map +7 -0
  101. package/dist/src/tools/search-console/list-accessible-sites.js +42 -0
  102. package/dist/src/tools/search-console/list-accessible-sites.js.map +7 -0
  103. package/dist/src/tools/search-console/low-ctr-opportunities.js +98 -0
  104. package/dist/src/tools/search-console/low-ctr-opportunities.js.map +7 -0
  105. package/dist/src/tools/search-console/page-performance.js +104 -0
  106. package/dist/src/tools/search-console/page-performance.js.map +7 -0
  107. package/dist/src/tools/search-console/product-demand-low-capture-queries.js +93 -0
  108. package/dist/src/tools/search-console/product-demand-low-capture-queries.js.map +7 -0
  109. package/dist/src/tools/search-console/query-page-matrix.js +99 -0
  110. package/dist/src/tools/search-console/query-page-matrix.js.map +7 -0
  111. package/dist/src/tools/search-console/query-performance.js +109 -0
  112. package/dist/src/tools/search-console/query-performance.js.map +7 -0
  113. package/dist/src/tools/search-console/quick-win-opportunities.js +93 -0
  114. package/dist/src/tools/search-console/quick-win-opportunities.js.map +7 -0
  115. package/dist/src/tools/search-console/rising-non-brand-queries.js +121 -0
  116. package/dist/src/tools/search-console/rising-non-brand-queries.js.map +7 -0
  117. package/dist/src/tools/search-console/search-performance.js +89 -0
  118. package/dist/src/tools/search-console/search-performance.js.map +7 -0
  119. package/dist/src/tools/search-console/site-context.js +43 -0
  120. package/dist/src/tools/search-console/site-context.js.map +7 -0
  121. package/dist/src/tools/search-console/visibility-declines.js +146 -0
  122. package/dist/src/tools/search-console/visibility-declines.js.map +7 -0
  123. package/dist/src/tools/vtex/computed-price.js +48 -0
  124. package/dist/src/tools/vtex/computed-price.js.map +7 -0
  125. package/dist/src/tools/vtex/index.js +11 -0
  126. package/dist/src/tools/vtex/index.js.map +7 -0
  127. package/dist/src/tools/vtex/inventory-check.js +148 -0
  128. package/dist/src/tools/vtex/inventory-check.js.map +7 -0
  129. package/dist/src/tools/vtex/order-details.js +56 -0
  130. package/dist/src/tools/vtex/order-details.js.map +7 -0
  131. package/dist/src/tools/vtex/orders-summary.js +83 -0
  132. package/dist/src/tools/vtex/orders-summary.js.map +7 -0
  133. package/dist/src/tools/vtex/product-offers.js +28 -0
  134. package/dist/src/tools/vtex/product-offers.js.map +7 -0
  135. package/dist/src/tools/vtex/sku-offers.js +30 -0
  136. package/dist/src/tools/vtex/sku-offers.js.map +7 -0
  137. package/dist/src/tools/vtex/sku-price.js +42 -0
  138. package/dist/src/tools/vtex/sku-price.js.map +7 -0
  139. package/dist/src/tools/vtex/update-inventory.js +43 -0
  140. package/dist/src/tools/vtex/update-inventory.js.map +7 -0
  141. package/dist/src/tools/vtex/update-lead-time.js +32 -0
  142. package/dist/src/tools/vtex/update-lead-time.js.map +7 -0
  143. package/dist/src/tools/vtex/warehouse-inventory.js +42 -0
  144. package/dist/src/tools/vtex/warehouse-inventory.js.map +7 -0
  145. package/dist/src/utils/case-conversion.js +21 -0
  146. package/dist/src/utils/case-conversion.js.map +7 -0
  147. package/dist/src/utils/currency.js +52 -0
  148. package/dist/src/utils/currency.js.map +7 -0
  149. package/dist/src/utils/format-order-details.js +137 -0
  150. package/dist/src/utils/format-order-details.js.map +7 -0
  151. package/dist/src/utils/google-ads.js +78 -0
  152. package/dist/src/utils/google-ads.js.map +7 -0
  153. package/dist/src/utils/money.js +83 -0
  154. package/dist/src/utils/money.js.map +7 -0
  155. package/dist/src/utils/order-status.js +11 -0
  156. package/dist/src/utils/order-status.js.map +7 -0
  157. package/dist/src/utils/pagination.js +45 -0
  158. package/dist/src/utils/pagination.js.map +7 -0
  159. package/dist/src/utils/strip-payload.js +40 -0
  160. package/dist/src/utils/strip-payload.js.map +7 -0
  161. package/dist/src/utils/type-guards.js +7 -0
  162. package/dist/src/utils/type-guards.js.map +7 -0
  163. package/package.json +66 -0
  164. package/public/favicon.ico +0 -0
  165. 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