@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,267 @@
1
+ import { error, object } from "mcp-use/server";
2
+ import { z } from "zod";
3
+ import {
4
+ dateRegex,
5
+ getFieldValue,
6
+ microsToCurrency,
7
+ parseGoogleAdsMetricValue,
8
+ resolveGoogleAdsCustomerId,
9
+ resolveGoogleAdsDeveloperToken,
10
+ resolveGoogleAdsLoginCustomerId,
11
+ round,
12
+ toPercent
13
+ } from "../../utils/google-ads.js";
14
+ import { searchGoogleAds } from "../../services/google-ads/google-ads-client.js";
15
+ import { stripNulls } from "../../utils/strip-payload.js";
16
+ function buildRisk(code, severity, title, detail, metricValue) {
17
+ return stripNulls({
18
+ code,
19
+ severity,
20
+ title,
21
+ detail,
22
+ metric_value: metricValue !== void 0 ? round(metricValue) : void 0
23
+ });
24
+ }
25
+ const googleAdsAccountRisksSchema = z.object({
26
+ startDate: z.string().regex(dateRegex).describe("Start date in YYYY-MM-DD format."),
27
+ endDate: z.string().regex(dateRegex).describe("End date in YYYY-MM-DD format."),
28
+ customerId: z.string().describe("Google Ads customer ID to query. Accepts digits with or without hyphens."),
29
+ loginCustomerId: z.string().optional().describe(
30
+ "Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
31
+ )
32
+ });
33
+ async function googleAdsAccountRisksHandler(params) {
34
+ try {
35
+ const customerId = resolveGoogleAdsCustomerId(params.customerId);
36
+ const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
37
+ const developerToken = resolveGoogleAdsDeveloperToken();
38
+ const [campaignsResult, channelsResult, seriesResult] = await Promise.all([
39
+ searchGoogleAds(
40
+ customerId,
41
+ [
42
+ "SELECT",
43
+ "customer.descriptive_name,",
44
+ "customer.currency_code,",
45
+ "campaign.id,",
46
+ "campaign.name,",
47
+ "campaign.advertising_channel_type,",
48
+ "metrics.impressions,",
49
+ "metrics.clicks,",
50
+ "metrics.cost_micros,",
51
+ "metrics.conversions,",
52
+ "metrics.conversions_value",
53
+ "FROM campaign",
54
+ `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
55
+ "AND campaign.status = 'ENABLED'",
56
+ "ORDER BY metrics.cost_micros DESC"
57
+ ].join(" "),
58
+ developerToken,
59
+ loginCustomerId
60
+ ),
61
+ searchGoogleAds(
62
+ customerId,
63
+ [
64
+ "SELECT",
65
+ "campaign.advertising_channel_type,",
66
+ "metrics.cost_micros,",
67
+ "metrics.conversions_value",
68
+ "FROM campaign",
69
+ `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
70
+ "AND campaign.status = 'ENABLED'"
71
+ ].join(" "),
72
+ developerToken,
73
+ loginCustomerId
74
+ ),
75
+ searchGoogleAds(
76
+ customerId,
77
+ [
78
+ "SELECT",
79
+ "segments.date,",
80
+ "metrics.cost_micros,",
81
+ "metrics.conversions_value,",
82
+ "metrics.conversions",
83
+ "FROM customer",
84
+ `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
85
+ "ORDER BY segments.date"
86
+ ].join(" "),
87
+ developerToken,
88
+ loginCustomerId
89
+ )
90
+ ]);
91
+ const campaignRows = campaignsResult.rows.map((row) => ({
92
+ campaign_name: getFieldValue(row, "campaign.name"),
93
+ channel_type: getFieldValue(row, "campaign.advertisingChannelType"),
94
+ cost: microsToCurrency(getFieldValue(row, "metrics.costMicros")),
95
+ conversions: round(parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))),
96
+ conversion_value: round(
97
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
98
+ )
99
+ }));
100
+ const totalCost = campaignRows.reduce((sum, row) => sum + row.cost, 0);
101
+ const totalConversions = campaignRows.reduce((sum, row) => sum + row.conversions, 0);
102
+ const totalConversionValue = campaignRows.reduce((sum, row) => sum + row.conversion_value, 0);
103
+ const topCampaign = campaignRows[0];
104
+ const topCampaignShare = round(toPercent(topCampaign?.cost ?? 0, totalCost));
105
+ const channelCosts = /* @__PURE__ */ new Map();
106
+ for (const row of channelsResult.rows) {
107
+ const channelType = String(
108
+ getFieldValue(row, "campaign.advertisingChannelType", "UNKNOWN") ?? "UNKNOWN"
109
+ );
110
+ channelCosts.set(
111
+ channelType,
112
+ (channelCosts.get(channelType) ?? 0) + microsToCurrency(getFieldValue(row, "metrics.costMicros"))
113
+ );
114
+ }
115
+ const dominantChannel = Array.from(channelCosts.entries()).sort((a, b) => b[1] - a[1])[0];
116
+ const dominantChannelShare = round(toPercent(dominantChannel?.[1] ?? 0, totalCost));
117
+ const series = seriesResult.rows.map((row) => ({
118
+ period_start: String(getFieldValue(row, "segments.date") ?? ""),
119
+ cost: microsToCurrency(getFieldValue(row, "metrics.costMicros")),
120
+ conversion_value: round(
121
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
122
+ ),
123
+ conversions: round(parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions")))
124
+ }));
125
+ const midpoint = Math.ceil(series.length / 2);
126
+ const firstHalf = series.slice(0, midpoint);
127
+ const secondHalf = series.slice(midpoint);
128
+ const firstHalfRoas = round(
129
+ firstHalf.reduce((sum, item) => sum + item.conversion_value, 0) / firstHalf.reduce((sum, item) => sum + item.cost, 0)
130
+ );
131
+ const secondHalfRoas = round(
132
+ secondHalf.reduce((sum, item) => sum + item.conversion_value, 0) / secondHalf.reduce((sum, item) => sum + item.cost, 0)
133
+ );
134
+ const risks = [];
135
+ if (topCampaignShare >= 70) {
136
+ risks.push(
137
+ buildRisk(
138
+ "campaign_concentration_high",
139
+ "high",
140
+ "Concentraci\xF3n alta en una campa\xF1a",
141
+ `La campa\xF1a principal concentra ${topCampaignShare}% del gasto.`,
142
+ topCampaignShare
143
+ )
144
+ );
145
+ } else if (topCampaignShare >= 50) {
146
+ risks.push(
147
+ buildRisk(
148
+ "campaign_concentration_medium",
149
+ "medium",
150
+ "Concentraci\xF3n relevante en una campa\xF1a",
151
+ `La campa\xF1a principal concentra ${topCampaignShare}% del gasto.`,
152
+ topCampaignShare
153
+ )
154
+ );
155
+ }
156
+ if (dominantChannelShare >= 80) {
157
+ risks.push(
158
+ buildRisk(
159
+ "channel_concentration_high",
160
+ "high",
161
+ "Dependencia alta de un tipo de campa\xF1a",
162
+ `${dominantChannel?.[0] ?? "UNKNOWN"} concentra ${dominantChannelShare}% del gasto.`,
163
+ dominantChannelShare
164
+ )
165
+ );
166
+ } else if (dominantChannelShare >= 60) {
167
+ risks.push(
168
+ buildRisk(
169
+ "channel_concentration_medium",
170
+ "medium",
171
+ "Dependencia relevante de un tipo de campa\xF1a",
172
+ `${dominantChannel?.[0] ?? "UNKNOWN"} concentra ${dominantChannelShare}% del gasto.`,
173
+ dominantChannelShare
174
+ )
175
+ );
176
+ }
177
+ const overallRoas = round(totalConversionValue / totalCost);
178
+ if (overallRoas < 1) {
179
+ risks.push(
180
+ buildRisk(
181
+ "low_roas",
182
+ "high",
183
+ "ROAS por debajo de 1",
184
+ `El ROAS agregado es ${overallRoas}. La cuenta est\xE1 devolviendo menos valor que el gasto atribuido.`,
185
+ overallRoas
186
+ )
187
+ );
188
+ } else if (overallRoas < 1.5) {
189
+ risks.push(
190
+ buildRisk(
191
+ "borderline_roas",
192
+ "medium",
193
+ "ROAS ajustado al l\xEDmite",
194
+ `El ROAS agregado es ${overallRoas}. Hay poco margen antes de entrar en zona de riesgo.`,
195
+ overallRoas
196
+ )
197
+ );
198
+ }
199
+ if (totalConversions < 5) {
200
+ risks.push(
201
+ buildRisk(
202
+ "low_conversion_volume",
203
+ "medium",
204
+ "Volumen de conversiones bajo",
205
+ `La cuenta gener\xF3 ${round(totalConversions)} conversiones en el per\xEDodo. La se\xF1al estad\xEDstica es limitada.`,
206
+ totalConversions
207
+ )
208
+ );
209
+ }
210
+ if (firstHalf.length > 0 && secondHalf.length > 0 && secondHalfRoas < firstHalfRoas * 0.7) {
211
+ risks.push(
212
+ buildRisk(
213
+ "roas_deteriorating",
214
+ "medium",
215
+ "Deterioro reciente de eficiencia",
216
+ `El ROAS cay\xF3 de ${firstHalfRoas} en la primera mitad a ${secondHalfRoas} en la segunda mitad del per\xEDodo.`,
217
+ secondHalfRoas
218
+ )
219
+ );
220
+ }
221
+ const highSeverityCount = risks.filter((risk) => risk.severity === "high").length;
222
+ const mediumSeverityCount = risks.filter((risk) => risk.severity === "medium").length;
223
+ const overallSeverity = highSeverityCount > 0 ? "high" : mediumSeverityCount > 0 ? "medium" : "low";
224
+ return object(
225
+ stripNulls({
226
+ customer_id: customerId,
227
+ login_customer_id: loginCustomerId,
228
+ customer_name: getFieldValue(campaignsResult.rows[0] ?? {}, "customer.descriptiveName"),
229
+ currency_code: getFieldValue(campaignsResult.rows[0] ?? {}, "customer.currencyCode"),
230
+ date_range: {
231
+ start_date: params.startDate,
232
+ end_date: params.endDate
233
+ },
234
+ overview: {
235
+ total_campaigns: campaignRows.length,
236
+ total_cost: round(totalCost),
237
+ total_conversions: round(totalConversions),
238
+ total_conversion_value: round(totalConversionValue),
239
+ roas: overallRoas,
240
+ top_campaign_by_cost: topCampaign?.campaign_name,
241
+ top_campaign_cost_share_percent: topCampaignShare,
242
+ dominant_channel: dominantChannel?.[0],
243
+ dominant_channel_cost_share_percent: dominantChannelShare,
244
+ first_half_roas: firstHalfRoas,
245
+ second_half_roas: secondHalfRoas
246
+ },
247
+ risk_summary: {
248
+ overall_severity: overallSeverity,
249
+ high_severity_count: highSeverityCount,
250
+ medium_severity_count: mediumSeverityCount,
251
+ total_risks: risks.length
252
+ },
253
+ risks,
254
+ metadata: {
255
+ request_ids: [campaignsResult.requestId, channelsResult.requestId, seriesResult.requestId]
256
+ }
257
+ })
258
+ );
259
+ } catch (err) {
260
+ return error(err instanceof Error ? err.message : "Failed to evaluate Google Ads account risks");
261
+ }
262
+ }
263
+ export {
264
+ googleAdsAccountRisksHandler,
265
+ googleAdsAccountRisksSchema
266
+ };
267
+ //# sourceMappingURL=account-risks.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/tools/google-ads/account-risks.ts"],
4
+ "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n dateRegex,\n getFieldValue,\n microsToCurrency,\n parseGoogleAdsMetricValue,\n resolveGoogleAdsCustomerId,\n resolveGoogleAdsDeveloperToken,\n resolveGoogleAdsLoginCustomerId,\n round,\n toPercent,\n} from \"../../utils/google-ads.js\";\nimport { searchGoogleAds } from \"../../services/google-ads/google-ads-client.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\ntype RiskSeverity = \"low\" | \"medium\" | \"high\";\n\nfunction buildRisk(\n code: string,\n severity: RiskSeverity,\n title: string,\n detail: string,\n metricValue?: number\n) {\n return stripNulls({\n code,\n severity,\n title,\n detail,\n metric_value: metricValue !== undefined ? round(metricValue) : undefined,\n });\n}\n\nexport const googleAdsAccountRisksSchema = z.object({\n startDate: z.string().regex(dateRegex).describe(\"Start date in YYYY-MM-DD format.\"),\n endDate: z.string().regex(dateRegex).describe(\"End date in YYYY-MM-DD format.\"),\n customerId: z\n .string()\n .describe(\"Google Ads customer ID to query. Accepts digits with or without hyphens.\"),\n loginCustomerId: z\n .string()\n .optional()\n .describe(\n \"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured.\"\n ),\n});\n\nexport async function googleAdsAccountRisksHandler(\n params: z.infer<typeof googleAdsAccountRisksSchema>\n) {\n try {\n const customerId = resolveGoogleAdsCustomerId(params.customerId);\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n\n const [campaignsResult, channelsResult, seriesResult] = await Promise.all([\n searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"customer.descriptive_name,\",\n \"customer.currency_code,\",\n \"campaign.id,\",\n \"campaign.name,\",\n \"campaign.advertising_channel_type,\",\n \"metrics.impressions,\",\n \"metrics.clicks,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions,\",\n \"metrics.conversions_value\",\n \"FROM campaign\",\n `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,\n \"AND campaign.status = 'ENABLED'\",\n \"ORDER BY metrics.cost_micros DESC\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n ),\n searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"campaign.advertising_channel_type,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions_value\",\n \"FROM campaign\",\n `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,\n \"AND campaign.status = 'ENABLED'\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n ),\n searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"segments.date,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions_value,\",\n \"metrics.conversions\",\n \"FROM customer\",\n `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,\n \"ORDER BY segments.date\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n ),\n ]);\n\n const campaignRows = campaignsResult.rows.map((row) => ({\n campaign_name: getFieldValue(row, \"campaign.name\"),\n channel_type: getFieldValue(row, \"campaign.advertisingChannelType\"),\n cost: microsToCurrency(getFieldValue(row, \"metrics.costMicros\")),\n conversions: round(parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversions\"))),\n conversion_value: round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversionsValue\"))\n ),\n }));\n\n const totalCost = campaignRows.reduce((sum, row) => sum + row.cost, 0);\n const totalConversions = campaignRows.reduce((sum, row) => sum + row.conversions, 0);\n const totalConversionValue = campaignRows.reduce((sum, row) => sum + row.conversion_value, 0);\n const topCampaign = campaignRows[0];\n const topCampaignShare = round(toPercent(topCampaign?.cost ?? 0, totalCost));\n\n const channelCosts = new Map<string, number>();\n for (const row of channelsResult.rows) {\n const channelType = String(\n getFieldValue(row, \"campaign.advertisingChannelType\", \"UNKNOWN\") ?? \"UNKNOWN\"\n );\n channelCosts.set(\n channelType,\n (channelCosts.get(channelType) ?? 0) +\n microsToCurrency(getFieldValue(row, \"metrics.costMicros\"))\n );\n }\n const dominantChannel = Array.from(channelCosts.entries()).sort((a, b) => b[1] - a[1])[0];\n const dominantChannelShare = round(toPercent(dominantChannel?.[1] ?? 0, totalCost));\n\n const series = seriesResult.rows.map((row) => ({\n period_start: String(getFieldValue(row, \"segments.date\") ?? \"\"),\n cost: microsToCurrency(getFieldValue(row, \"metrics.costMicros\")),\n conversion_value: round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversionsValue\"))\n ),\n conversions: round(parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversions\"))),\n }));\n const midpoint = Math.ceil(series.length / 2);\n const firstHalf = series.slice(0, midpoint);\n const secondHalf = series.slice(midpoint);\n const firstHalfRoas = round(\n firstHalf.reduce((sum, item) => sum + item.conversion_value, 0) /\n firstHalf.reduce((sum, item) => sum + item.cost, 0)\n );\n const secondHalfRoas = round(\n secondHalf.reduce((sum, item) => sum + item.conversion_value, 0) /\n secondHalf.reduce((sum, item) => sum + item.cost, 0)\n );\n\n const risks = [];\n\n if (topCampaignShare >= 70) {\n risks.push(\n buildRisk(\n \"campaign_concentration_high\",\n \"high\",\n \"Concentraci\u00F3n alta en una campa\u00F1a\",\n `La campa\u00F1a principal concentra ${topCampaignShare}% del gasto.`,\n topCampaignShare\n )\n );\n } else if (topCampaignShare >= 50) {\n risks.push(\n buildRisk(\n \"campaign_concentration_medium\",\n \"medium\",\n \"Concentraci\u00F3n relevante en una campa\u00F1a\",\n `La campa\u00F1a principal concentra ${topCampaignShare}% del gasto.`,\n topCampaignShare\n )\n );\n }\n\n if (dominantChannelShare >= 80) {\n risks.push(\n buildRisk(\n \"channel_concentration_high\",\n \"high\",\n \"Dependencia alta de un tipo de campa\u00F1a\",\n `${dominantChannel?.[0] ?? \"UNKNOWN\"} concentra ${dominantChannelShare}% del gasto.`,\n dominantChannelShare\n )\n );\n } else if (dominantChannelShare >= 60) {\n risks.push(\n buildRisk(\n \"channel_concentration_medium\",\n \"medium\",\n \"Dependencia relevante de un tipo de campa\u00F1a\",\n `${dominantChannel?.[0] ?? \"UNKNOWN\"} concentra ${dominantChannelShare}% del gasto.`,\n dominantChannelShare\n )\n );\n }\n\n const overallRoas = round(totalConversionValue / totalCost);\n if (overallRoas < 1) {\n risks.push(\n buildRisk(\n \"low_roas\",\n \"high\",\n \"ROAS por debajo de 1\",\n `El ROAS agregado es ${overallRoas}. La cuenta est\u00E1 devolviendo menos valor que el gasto atribuido.`,\n overallRoas\n )\n );\n } else if (overallRoas < 1.5) {\n risks.push(\n buildRisk(\n \"borderline_roas\",\n \"medium\",\n \"ROAS ajustado al l\u00EDmite\",\n `El ROAS agregado es ${overallRoas}. Hay poco margen antes de entrar en zona de riesgo.`,\n overallRoas\n )\n );\n }\n\n if (totalConversions < 5) {\n risks.push(\n buildRisk(\n \"low_conversion_volume\",\n \"medium\",\n \"Volumen de conversiones bajo\",\n `La cuenta gener\u00F3 ${round(totalConversions)} conversiones en el per\u00EDodo. La se\u00F1al estad\u00EDstica es limitada.`,\n totalConversions\n )\n );\n }\n\n if (firstHalf.length > 0 && secondHalf.length > 0 && secondHalfRoas < firstHalfRoas * 0.7) {\n risks.push(\n buildRisk(\n \"roas_deteriorating\",\n \"medium\",\n \"Deterioro reciente de eficiencia\",\n `El ROAS cay\u00F3 de ${firstHalfRoas} en la primera mitad a ${secondHalfRoas} en la segunda mitad del per\u00EDodo.`,\n secondHalfRoas\n )\n );\n }\n\n const highSeverityCount = risks.filter((risk) => risk.severity === \"high\").length;\n const mediumSeverityCount = risks.filter((risk) => risk.severity === \"medium\").length;\n const overallSeverity: RiskSeverity =\n highSeverityCount > 0 ? \"high\" : mediumSeverityCount > 0 ? \"medium\" : \"low\";\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer_name: getFieldValue(campaignsResult.rows[0] ?? {}, \"customer.descriptiveName\"),\n currency_code: getFieldValue(campaignsResult.rows[0] ?? {}, \"customer.currencyCode\"),\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n overview: {\n total_campaigns: campaignRows.length,\n total_cost: round(totalCost),\n total_conversions: round(totalConversions),\n total_conversion_value: round(totalConversionValue),\n roas: overallRoas,\n top_campaign_by_cost: topCampaign?.campaign_name,\n top_campaign_cost_share_percent: topCampaignShare,\n dominant_channel: dominantChannel?.[0],\n dominant_channel_cost_share_percent: dominantChannelShare,\n first_half_roas: firstHalfRoas,\n second_half_roas: secondHalfRoas,\n },\n risk_summary: {\n overall_severity: overallSeverity,\n high_severity_count: highSeverityCount,\n medium_severity_count: mediumSeverityCount,\n total_risks: risks.length,\n },\n risks,\n metadata: {\n request_ids: [campaignsResult.requestId, channelsResult.requestId, seriesResult.requestId],\n },\n })\n );\n } catch (err) {\n return error(err instanceof Error ? err.message : \"Failed to evaluate Google Ads account risks\");\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,107 @@
1
+ import { error, object } from "mcp-use/server";
2
+ import { z } from "zod";
3
+ import {
4
+ dateRegex,
5
+ getFieldValue,
6
+ microsToCurrency,
7
+ parseGoogleAdsMetricValue,
8
+ resolveGoogleAdsCustomerId,
9
+ resolveGoogleAdsDeveloperToken,
10
+ resolveGoogleAdsLoginCustomerId,
11
+ round
12
+ } from "../../utils/google-ads.js";
13
+ import { searchGoogleAds } from "../../services/google-ads/google-ads-client.js";
14
+ import { stripNulls } from "../../utils/strip-payload.js";
15
+ const googleAdsBreakEvenAnalysisSchema = z.object({
16
+ startDate: z.string().regex(dateRegex).describe("Start date in YYYY-MM-DD format."),
17
+ endDate: z.string().regex(dateRegex).describe("End date in YYYY-MM-DD format."),
18
+ customerId: z.string().describe("Google Ads customer ID to query. Accepts digits with or without hyphens."),
19
+ loginCustomerId: z.string().optional().describe(
20
+ "Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
21
+ ),
22
+ breakEvenRoas: z.number().positive().describe("Break-even ROAS for the business. Example: 2.5 means the account needs at least 2.5x return to break even."),
23
+ targetCpa: z.number().positive().optional().describe("Optional maximum acceptable CPA for the business.")
24
+ });
25
+ async function googleAdsBreakEvenAnalysisHandler(params) {
26
+ try {
27
+ const customerId = resolveGoogleAdsCustomerId(params.customerId);
28
+ const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
29
+ const developerToken = resolveGoogleAdsDeveloperToken();
30
+ const result = await searchGoogleAds(
31
+ customerId,
32
+ [
33
+ "SELECT",
34
+ "customer.descriptive_name,",
35
+ "customer.currency_code,",
36
+ "metrics.clicks,",
37
+ "metrics.cost_micros,",
38
+ "metrics.conversions,",
39
+ "metrics.conversions_value",
40
+ "FROM customer",
41
+ `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`
42
+ ].join(" "),
43
+ developerToken,
44
+ loginCustomerId
45
+ );
46
+ const row = result.rows[0] ?? {};
47
+ const clicks = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
48
+ const cost = microsToCurrency(getFieldValue(row, "metrics.costMicros"));
49
+ const conversions = round(
50
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
51
+ );
52
+ const conversionValue = round(
53
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
54
+ );
55
+ const currentRoas = round(conversionValue / cost);
56
+ const currentCpa = round(cost / conversions);
57
+ const roasGap = round(currentRoas - params.breakEvenRoas);
58
+ const roasGapPercent = round((currentRoas / params.breakEvenRoas - 1) * 100);
59
+ return object(
60
+ stripNulls({
61
+ customer_id: customerId,
62
+ login_customer_id: loginCustomerId,
63
+ customer_name: getFieldValue(row, "customer.descriptiveName"),
64
+ currency_code: getFieldValue(row, "customer.currencyCode"),
65
+ date_range: {
66
+ start_date: params.startDate,
67
+ end_date: params.endDate
68
+ },
69
+ inputs: {
70
+ break_even_roas: params.breakEvenRoas,
71
+ target_cpa: params.targetCpa
72
+ },
73
+ overview: {
74
+ clicks,
75
+ cost,
76
+ conversions,
77
+ conversion_value: conversionValue,
78
+ current_roas: currentRoas,
79
+ current_cpa: currentCpa,
80
+ break_even_roas: params.breakEvenRoas,
81
+ roas_gap: roasGap,
82
+ roas_gap_percent: roasGapPercent,
83
+ is_above_break_even: currentRoas >= params.breakEvenRoas,
84
+ cpa_vs_target_gap: params.targetCpa ? round(currentCpa - params.targetCpa) : void 0,
85
+ is_cpa_within_target: params.targetCpa ? currentCpa > 0 && currentCpa <= params.targetCpa : void 0
86
+ },
87
+ interpretation: {
88
+ status: currentRoas >= params.breakEvenRoas ? "above_break_even" : "below_break_even",
89
+ message: currentRoas >= params.breakEvenRoas ? `The account is above break-even by ${roasGap}.` : `The account is below break-even by ${Math.abs(roasGap)}.`
90
+ },
91
+ metadata: {
92
+ row_count: result.rows.length,
93
+ request_id: result.requestId
94
+ }
95
+ })
96
+ );
97
+ } catch (err) {
98
+ return error(
99
+ err instanceof Error ? err.message : "Failed to run Google Ads break-even analysis"
100
+ );
101
+ }
102
+ }
103
+ export {
104
+ googleAdsBreakEvenAnalysisHandler,
105
+ googleAdsBreakEvenAnalysisSchema
106
+ };
107
+ //# sourceMappingURL=break-even-analysis.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/tools/google-ads/break-even-analysis.ts"],
4
+ "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n dateRegex,\n getFieldValue,\n microsToCurrency,\n parseGoogleAdsMetricValue,\n resolveGoogleAdsCustomerId,\n resolveGoogleAdsDeveloperToken,\n resolveGoogleAdsLoginCustomerId,\n round,\n} from \"../../utils/google-ads.js\";\nimport { searchGoogleAds } from \"../../services/google-ads/google-ads-client.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nexport const googleAdsBreakEvenAnalysisSchema = z.object({\n startDate: z.string().regex(dateRegex).describe(\"Start date in YYYY-MM-DD format.\"),\n endDate: z.string().regex(dateRegex).describe(\"End date in YYYY-MM-DD format.\"),\n customerId: z\n .string()\n .describe(\"Google Ads customer ID to query. Accepts digits with or without hyphens.\"),\n loginCustomerId: z\n .string()\n .optional()\n .describe(\n \"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured.\"\n ),\n breakEvenRoas: z\n .number()\n .positive()\n .describe(\"Break-even ROAS for the business. Example: 2.5 means the account needs at least 2.5x return to break even.\"),\n targetCpa: z\n .number()\n .positive()\n .optional()\n .describe(\"Optional maximum acceptable CPA for the business.\"),\n});\n\nexport async function googleAdsBreakEvenAnalysisHandler(\n params: z.infer<typeof googleAdsBreakEvenAnalysisSchema>\n) {\n try {\n const customerId = resolveGoogleAdsCustomerId(params.customerId);\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n\n const result = await searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"customer.descriptive_name,\",\n \"customer.currency_code,\",\n \"metrics.clicks,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions,\",\n \"metrics.conversions_value\",\n \"FROM customer\",\n `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const row = result.rows[0] ?? {};\n const clicks = parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.clicks\"));\n const cost = microsToCurrency(getFieldValue(row, \"metrics.costMicros\"));\n const conversions = round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversions\"))\n );\n const conversionValue = round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversionsValue\"))\n );\n const currentRoas = round(conversionValue / cost);\n const currentCpa = round(cost / conversions);\n const roasGap = round(currentRoas - params.breakEvenRoas);\n const roasGapPercent = round((currentRoas / params.breakEvenRoas - 1) * 100);\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer_name: getFieldValue(row, \"customer.descriptiveName\"),\n currency_code: getFieldValue(row, \"customer.currencyCode\"),\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n inputs: {\n break_even_roas: params.breakEvenRoas,\n target_cpa: params.targetCpa,\n },\n overview: {\n clicks,\n cost,\n conversions,\n conversion_value: conversionValue,\n current_roas: currentRoas,\n current_cpa: currentCpa,\n break_even_roas: params.breakEvenRoas,\n roas_gap: roasGap,\n roas_gap_percent: roasGapPercent,\n is_above_break_even: currentRoas >= params.breakEvenRoas,\n cpa_vs_target_gap: params.targetCpa ? round(currentCpa - params.targetCpa) : undefined,\n is_cpa_within_target: params.targetCpa\n ? currentCpa > 0 && currentCpa <= params.targetCpa\n : undefined,\n },\n interpretation: {\n status:\n currentRoas >= params.breakEvenRoas\n ? \"above_break_even\"\n : \"below_break_even\",\n message:\n currentRoas >= params.breakEvenRoas\n ? `The account is above break-even by ${roasGap}.`\n : `The account is below break-even by ${Math.abs(roasGap)}.`,\n },\n metadata: {\n row_count: result.rows.length,\n request_id: result.requestId,\n },\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to run Google Ads break-even analysis\"\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,157 @@
1
+ import { error, object } from "mcp-use/server";
2
+ import { z } from "zod";
3
+ import {
4
+ dateRegex,
5
+ getFieldValue,
6
+ microsToCurrency,
7
+ parseGoogleAdsMetricValue,
8
+ resolveGoogleAdsCustomerId,
9
+ resolveGoogleAdsDeveloperToken,
10
+ resolveGoogleAdsLoginCustomerId,
11
+ round,
12
+ toPercent
13
+ } from "../../utils/google-ads.js";
14
+ import { searchGoogleAds } from "../../services/google-ads/google-ads-client.js";
15
+ import { stripNulls } from "../../utils/strip-payload.js";
16
+ const campaignStatusEnum = z.enum(["ENABLED", "PAUSED", "REMOVED"]);
17
+ const googleAdsCampaignPerformanceSchema = z.object({
18
+ startDate: z.string().regex(dateRegex).describe("Start date in YYYY-MM-DD format."),
19
+ endDate: z.string().regex(dateRegex).describe("End date in YYYY-MM-DD format."),
20
+ customerId: z.string().describe("Google Ads customer ID to query. Accepts digits with or without hyphens."),
21
+ loginCustomerId: z.string().optional().describe(
22
+ "Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
23
+ ),
24
+ enabledOnly: z.boolean().optional().default(true).describe("If true, only returns enabled campaigns unless statusFilter is explicitly provided."),
25
+ statusFilter: z.array(campaignStatusEnum).optional().describe("Optional list of campaign statuses to include. Overrides enabledOnly when provided.")
26
+ });
27
+ async function googleAdsCampaignPerformanceHandler(params) {
28
+ try {
29
+ const customerId = resolveGoogleAdsCustomerId(params.customerId);
30
+ const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
31
+ const developerToken = resolveGoogleAdsDeveloperToken();
32
+ const effectiveStatuses = params.statusFilter && params.statusFilter.length > 0 ? params.statusFilter : params.enabledOnly !== false ? ["ENABLED"] : void 0;
33
+ const whereClauses = [`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`];
34
+ if (effectiveStatuses?.length) {
35
+ whereClauses.push(
36
+ `campaign.status IN (${effectiveStatuses.map((status) => `'${status}'`).join(", ")})`
37
+ );
38
+ }
39
+ const result = await searchGoogleAds(
40
+ customerId,
41
+ [
42
+ "SELECT",
43
+ "customer.descriptive_name,",
44
+ "customer.currency_code,",
45
+ "campaign.id,",
46
+ "campaign.name,",
47
+ "campaign.status,",
48
+ "campaign.advertising_channel_type,",
49
+ "metrics.impressions,",
50
+ "metrics.clicks,",
51
+ "metrics.cost_micros,",
52
+ "metrics.conversions,",
53
+ "metrics.conversions_value,",
54
+ "metrics.ctr,",
55
+ "metrics.average_cpc",
56
+ "FROM campaign",
57
+ `WHERE ${whereClauses.join(" AND ")}`,
58
+ "ORDER BY metrics.cost_micros DESC"
59
+ ].join(" "),
60
+ developerToken,
61
+ loginCustomerId
62
+ );
63
+ const campaigns = result.rows.map((row) => {
64
+ const impressions = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.impressions"));
65
+ const clicks = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
66
+ const cost = microsToCurrency(getFieldValue(row, "metrics.costMicros"));
67
+ const conversions = round(
68
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
69
+ );
70
+ const conversionValue = round(
71
+ parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
72
+ );
73
+ return {
74
+ campaign_id: getFieldValue(row, "campaign.id"),
75
+ campaign_name: getFieldValue(row, "campaign.name"),
76
+ status: getFieldValue(row, "campaign.status"),
77
+ channel_type: getFieldValue(row, "campaign.advertisingChannelType"),
78
+ impressions,
79
+ clicks,
80
+ cost,
81
+ conversions,
82
+ conversion_value: conversionValue,
83
+ ctr_percent: round(parseGoogleAdsMetricValue(getFieldValue(row, "metrics.ctr")) * 100),
84
+ average_cpc: microsToCurrency(getFieldValue(row, "metrics.averageCpc")),
85
+ cpa: round(cost / conversions),
86
+ roas: round(conversionValue / cost),
87
+ conversion_rate_percent: round(toPercent(conversions, clicks)),
88
+ cost_share_percent: 0
89
+ };
90
+ });
91
+ const totals = campaigns.reduce(
92
+ (acc, campaign) => {
93
+ acc.impressions += campaign.impressions;
94
+ acc.clicks += campaign.clicks;
95
+ acc.cost += campaign.cost;
96
+ acc.conversions += campaign.conversions;
97
+ acc.conversion_value += campaign.conversion_value;
98
+ return acc;
99
+ },
100
+ {
101
+ impressions: 0,
102
+ clicks: 0,
103
+ cost: 0,
104
+ conversions: 0,
105
+ conversion_value: 0
106
+ }
107
+ );
108
+ const campaignsWithShare = campaigns.map((campaign) => ({
109
+ ...campaign,
110
+ cost_share_percent: round(toPercent(campaign.cost, totals.cost))
111
+ }));
112
+ return object(
113
+ stripNulls({
114
+ customer_id: customerId,
115
+ login_customer_id: loginCustomerId,
116
+ date_range: {
117
+ start_date: params.startDate,
118
+ end_date: params.endDate
119
+ },
120
+ customer_name: getFieldValue(result.rows[0] ?? {}, "customer.descriptiveName"),
121
+ currency_code: getFieldValue(result.rows[0] ?? {}, "customer.currencyCode"),
122
+ applied_filters: {
123
+ enabled_only: params.enabledOnly !== false,
124
+ statuses: effectiveStatuses
125
+ },
126
+ overview: {
127
+ total_campaigns: campaignsWithShare.length,
128
+ impressions: totals.impressions,
129
+ clicks: totals.clicks,
130
+ cost: round(totals.cost),
131
+ conversions: round(totals.conversions),
132
+ conversion_value: round(totals.conversion_value),
133
+ ctr_percent: round(toPercent(totals.clicks, totals.impressions)),
134
+ average_cpc: round(totals.cost / totals.clicks),
135
+ cpa: round(totals.cost / totals.conversions),
136
+ roas: round(totals.conversion_value / totals.cost),
137
+ conversion_rate_percent: round(toPercent(totals.conversions, totals.clicks)),
138
+ top_campaign_by_cost: campaignsWithShare[0]?.campaign_name
139
+ },
140
+ campaigns: campaignsWithShare,
141
+ metadata: {
142
+ row_count: result.rows.length,
143
+ request_id: result.requestId
144
+ }
145
+ })
146
+ );
147
+ } catch (err) {
148
+ return error(
149
+ err instanceof Error ? err.message : "Failed to fetch Google Ads campaign performance"
150
+ );
151
+ }
152
+ }
153
+ export {
154
+ googleAdsCampaignPerformanceHandler,
155
+ googleAdsCampaignPerformanceSchema
156
+ };
157
+ //# sourceMappingURL=campaign-performance.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/tools/google-ads/campaign-performance.ts"],
4
+ "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n dateRegex,\n getFieldValue,\n microsToCurrency,\n parseGoogleAdsMetricValue,\n resolveGoogleAdsCustomerId,\n resolveGoogleAdsDeveloperToken,\n resolveGoogleAdsLoginCustomerId,\n round,\n toPercent,\n} from \"../../utils/google-ads.js\";\nimport { searchGoogleAds } from \"../../services/google-ads/google-ads-client.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\n\nconst campaignStatusEnum = z.enum([\"ENABLED\", \"PAUSED\", \"REMOVED\"]);\n\nexport const googleAdsCampaignPerformanceSchema = z.object({\n startDate: z.string().regex(dateRegex).describe(\"Start date in YYYY-MM-DD format.\"),\n endDate: z.string().regex(dateRegex).describe(\"End date in YYYY-MM-DD format.\"),\n customerId: z.string().describe(\"Google Ads customer ID to query. Accepts digits with or without hyphens.\"),\n loginCustomerId: z\n .string()\n .optional()\n .describe(\n \"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured.\"\n ),\n enabledOnly: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"If true, only returns enabled campaigns unless statusFilter is explicitly provided.\"),\n statusFilter: z\n .array(campaignStatusEnum)\n .optional()\n .describe(\"Optional list of campaign statuses to include. Overrides enabledOnly when provided.\"),\n});\n\nexport async function googleAdsCampaignPerformanceHandler(\n params: z.infer<typeof googleAdsCampaignPerformanceSchema>\n) {\n try {\n const customerId = resolveGoogleAdsCustomerId(params.customerId);\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n const effectiveStatuses =\n params.statusFilter && params.statusFilter.length > 0\n ? params.statusFilter\n : params.enabledOnly !== false\n ? [\"ENABLED\"]\n : undefined;\n const whereClauses = [`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`];\n\n if (effectiveStatuses?.length) {\n whereClauses.push(\n `campaign.status IN (${effectiveStatuses.map((status) => `'${status}'`).join(\", \")})`\n );\n }\n\n const result = await searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"customer.descriptive_name,\",\n \"customer.currency_code,\",\n \"campaign.id,\",\n \"campaign.name,\",\n \"campaign.status,\",\n \"campaign.advertising_channel_type,\",\n \"metrics.impressions,\",\n \"metrics.clicks,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions,\",\n \"metrics.conversions_value,\",\n \"metrics.ctr,\",\n \"metrics.average_cpc\",\n \"FROM campaign\",\n `WHERE ${whereClauses.join(\" AND \")}`,\n \"ORDER BY metrics.cost_micros DESC\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const campaigns = result.rows.map((row) => {\n const impressions = parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.impressions\"));\n const clicks = parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.clicks\"));\n const cost = microsToCurrency(getFieldValue(row, \"metrics.costMicros\"));\n const conversions = round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversions\"))\n );\n const conversionValue = round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversionsValue\"))\n );\n\n return {\n campaign_id: getFieldValue(row, \"campaign.id\"),\n campaign_name: getFieldValue(row, \"campaign.name\"),\n status: getFieldValue(row, \"campaign.status\"),\n channel_type: getFieldValue(row, \"campaign.advertisingChannelType\"),\n impressions,\n clicks,\n cost,\n conversions,\n conversion_value: conversionValue,\n ctr_percent: round(parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.ctr\")) * 100),\n average_cpc: microsToCurrency(getFieldValue(row, \"metrics.averageCpc\")),\n cpa: round(cost / conversions),\n roas: round(conversionValue / cost),\n conversion_rate_percent: round(toPercent(conversions, clicks)),\n cost_share_percent: 0,\n };\n });\n\n const totals = campaigns.reduce(\n (acc, campaign) => {\n acc.impressions += campaign.impressions;\n acc.clicks += campaign.clicks;\n acc.cost += campaign.cost;\n acc.conversions += campaign.conversions;\n acc.conversion_value += campaign.conversion_value;\n return acc;\n },\n {\n impressions: 0,\n clicks: 0,\n cost: 0,\n conversions: 0,\n conversion_value: 0,\n }\n );\n const campaignsWithShare = campaigns.map((campaign) => ({\n ...campaign,\n cost_share_percent: round(toPercent(campaign.cost, totals.cost)),\n }));\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n customer_name: getFieldValue(result.rows[0] ?? {}, \"customer.descriptiveName\"),\n currency_code: getFieldValue(result.rows[0] ?? {}, \"customer.currencyCode\"),\n applied_filters: {\n enabled_only: params.enabledOnly !== false,\n statuses: effectiveStatuses,\n },\n overview: {\n total_campaigns: campaignsWithShare.length,\n impressions: totals.impressions,\n clicks: totals.clicks,\n cost: round(totals.cost),\n conversions: round(totals.conversions),\n conversion_value: round(totals.conversion_value),\n ctr_percent: round(toPercent(totals.clicks, totals.impressions)),\n average_cpc: round(totals.cost / totals.clicks),\n cpa: round(totals.cost / totals.conversions),\n roas: round(totals.conversion_value / totals.cost),\n conversion_rate_percent: round(toPercent(totals.conversions, totals.clicks)),\n top_campaign_by_cost: campaignsWithShare[0]?.campaign_name,\n },\n campaigns: campaignsWithShare,\n metadata: {\n row_count: result.rows.length,\n request_id: result.requestId,\n },\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to fetch Google Ads campaign performance\"\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }