@yoryoboy/bi-mcp 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +346 -0
- package/bin/bi-mcp.js +2 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.js +768 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp-use.json +7 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/icon.svg +6 -0
- package/dist/src/analytics/ga4-channel-groups.js +20 -0
- package/dist/src/analytics/ga4-channel-groups.js.map +7 -0
- package/dist/src/analytics/ga4-report-utils.js +117 -0
- package/dist/src/analytics/ga4-report-utils.js.map +7 -0
- package/dist/src/config/benchmarks.js +128 -0
- package/dist/src/config/benchmarks.js.map +7 -0
- package/dist/src/config/google.js +41 -0
- package/dist/src/config/google.js.map +7 -0
- package/dist/src/config/vtex.js +26 -0
- package/dist/src/config/vtex.js.map +7 -0
- package/dist/src/google-ads/report-utils.js +78 -0
- package/dist/src/google-ads/report-utils.js.map +7 -0
- package/dist/src/prompts/reporte-ventas.js +75 -0
- package/dist/src/prompts/reporte-ventas.js.map +7 -0
- package/dist/src/search-console/search-console-utils.js +275 -0
- package/dist/src/search-console/search-console-utils.js.map +7 -0
- package/dist/src/services/analytics/ga4-client.js +69 -0
- package/dist/src/services/analytics/ga4-client.js.map +7 -0
- package/dist/src/services/analytics/oauth.js +30 -0
- package/dist/src/services/analytics/oauth.js.map +7 -0
- package/dist/src/services/google-ads/google-ads-client.js +54 -0
- package/dist/src/services/google-ads/google-ads-client.js.map +7 -0
- package/dist/src/services/search-console/search-console-client.js +45 -0
- package/dist/src/services/search-console/search-console-client.js.map +7 -0
- package/dist/src/services/vtex/vtex-api.js +51 -0
- package/dist/src/services/vtex/vtex-api.js.map +7 -0
- package/dist/src/services/vtex/vtex-catalog.js +18 -0
- package/dist/src/services/vtex/vtex-catalog.js.map +7 -0
- package/dist/src/services/vtex/vtex-logistics.js +151 -0
- package/dist/src/services/vtex/vtex-logistics.js.map +7 -0
- package/dist/src/services/vtex/vtex-orders.js +143 -0
- package/dist/src/services/vtex/vtex-orders.js.map +7 -0
- package/dist/src/services/vtex/vtex-pricing.js +17 -0
- package/dist/src/services/vtex/vtex-pricing.js.map +7 -0
- package/dist/src/tools/analytics/attribution-gaps.js +109 -0
- package/dist/src/tools/analytics/attribution-gaps.js.map +7 -0
- package/dist/src/tools/analytics/channel-mix.js +74 -0
- package/dist/src/tools/analytics/channel-mix.js.map +7 -0
- package/dist/src/tools/analytics/ecommerce-tracking-health.js +89 -0
- package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +7 -0
- package/dist/src/tools/analytics/engagement-overview.js +71 -0
- package/dist/src/tools/analytics/engagement-overview.js.map +7 -0
- package/dist/src/tools/analytics/index.js +12 -0
- package/dist/src/tools/analytics/index.js.map +7 -0
- package/dist/src/tools/analytics/list-accessible-properties.js +46 -0
- package/dist/src/tools/analytics/list-accessible-properties.js.map +7 -0
- package/dist/src/tools/analytics/property-info.js +54 -0
- package/dist/src/tools/analytics/property-info.js.map +7 -0
- package/dist/src/tools/analytics/revenue-by-channel.js +70 -0
- package/dist/src/tools/analytics/revenue-by-channel.js.map +7 -0
- package/dist/src/tools/analytics/revenue-overview.js +77 -0
- package/dist/src/tools/analytics/revenue-overview.js.map +7 -0
- package/dist/src/tools/analytics/revenue-trend.js +69 -0
- package/dist/src/tools/analytics/revenue-trend.js.map +7 -0
- package/dist/src/tools/analytics/source-medium-breakdown.js +86 -0
- package/dist/src/tools/analytics/source-medium-breakdown.js.map +7 -0
- package/dist/src/tools/analytics/top-landing-pages.js +79 -0
- package/dist/src/tools/analytics/top-landing-pages.js.map +7 -0
- package/dist/src/tools/google-ads/account-overview.js +103 -0
- package/dist/src/tools/google-ads/account-overview.js.map +7 -0
- package/dist/src/tools/google-ads/account-risks.js +267 -0
- package/dist/src/tools/google-ads/account-risks.js.map +7 -0
- package/dist/src/tools/google-ads/break-even-analysis.js +107 -0
- package/dist/src/tools/google-ads/break-even-analysis.js.map +7 -0
- package/dist/src/tools/google-ads/campaign-performance.js +157 -0
- package/dist/src/tools/google-ads/campaign-performance.js.map +7 -0
- package/dist/src/tools/google-ads/channel-mix.js +129 -0
- package/dist/src/tools/google-ads/channel-mix.js.map +7 -0
- package/dist/src/tools/google-ads/compare-accounts.js +122 -0
- package/dist/src/tools/google-ads/compare-accounts.js.map +7 -0
- package/dist/src/tools/google-ads/customer-clients.js +77 -0
- package/dist/src/tools/google-ads/customer-clients.js.map +7 -0
- package/dist/src/tools/google-ads/customer-info.js +64 -0
- package/dist/src/tools/google-ads/customer-info.js.map +7 -0
- package/dist/src/tools/google-ads/index.js +12 -0
- package/dist/src/tools/google-ads/index.js.map +7 -0
- package/dist/src/tools/google-ads/scaling-health.js +174 -0
- package/dist/src/tools/google-ads/scaling-health.js.map +7 -0
- package/dist/src/tools/google-ads/search-terms-summary.js +131 -0
- package/dist/src/tools/google-ads/search-terms-summary.js.map +7 -0
- package/dist/src/tools/google-ads/time-series.js +126 -0
- package/dist/src/tools/google-ads/time-series.js.map +7 -0
- package/dist/src/tools/index.js +5 -0
- package/dist/src/tools/index.js.map +7 -0
- package/dist/src/tools/search-console/country-breakdown.js +85 -0
- package/dist/src/tools/search-console/country-breakdown.js.map +7 -0
- package/dist/src/tools/search-console/device-breakdown.js +85 -0
- package/dist/src/tools/search-console/device-breakdown.js.map +7 -0
- package/dist/src/tools/search-console/high-impression-low-click-queries.js +95 -0
- package/dist/src/tools/search-console/high-impression-low-click-queries.js.map +7 -0
- package/dist/src/tools/search-console/index.js +15 -0
- package/dist/src/tools/search-console/index.js.map +7 -0
- package/dist/src/tools/search-console/list-accessible-sites.js +42 -0
- package/dist/src/tools/search-console/list-accessible-sites.js.map +7 -0
- package/dist/src/tools/search-console/low-ctr-opportunities.js +98 -0
- package/dist/src/tools/search-console/low-ctr-opportunities.js.map +7 -0
- package/dist/src/tools/search-console/page-performance.js +104 -0
- package/dist/src/tools/search-console/page-performance.js.map +7 -0
- package/dist/src/tools/search-console/product-demand-low-capture-queries.js +93 -0
- package/dist/src/tools/search-console/product-demand-low-capture-queries.js.map +7 -0
- package/dist/src/tools/search-console/query-page-matrix.js +99 -0
- package/dist/src/tools/search-console/query-page-matrix.js.map +7 -0
- package/dist/src/tools/search-console/query-performance.js +109 -0
- package/dist/src/tools/search-console/query-performance.js.map +7 -0
- package/dist/src/tools/search-console/quick-win-opportunities.js +93 -0
- package/dist/src/tools/search-console/quick-win-opportunities.js.map +7 -0
- package/dist/src/tools/search-console/rising-non-brand-queries.js +121 -0
- package/dist/src/tools/search-console/rising-non-brand-queries.js.map +7 -0
- package/dist/src/tools/search-console/search-performance.js +89 -0
- package/dist/src/tools/search-console/search-performance.js.map +7 -0
- package/dist/src/tools/search-console/site-context.js +43 -0
- package/dist/src/tools/search-console/site-context.js.map +7 -0
- package/dist/src/tools/search-console/visibility-declines.js +146 -0
- package/dist/src/tools/search-console/visibility-declines.js.map +7 -0
- package/dist/src/tools/vtex/computed-price.js +48 -0
- package/dist/src/tools/vtex/computed-price.js.map +7 -0
- package/dist/src/tools/vtex/index.js +11 -0
- package/dist/src/tools/vtex/index.js.map +7 -0
- package/dist/src/tools/vtex/inventory-check.js +148 -0
- package/dist/src/tools/vtex/inventory-check.js.map +7 -0
- package/dist/src/tools/vtex/order-details.js +56 -0
- package/dist/src/tools/vtex/order-details.js.map +7 -0
- package/dist/src/tools/vtex/orders-summary.js +83 -0
- package/dist/src/tools/vtex/orders-summary.js.map +7 -0
- package/dist/src/tools/vtex/product-offers.js +28 -0
- package/dist/src/tools/vtex/product-offers.js.map +7 -0
- package/dist/src/tools/vtex/sku-offers.js +30 -0
- package/dist/src/tools/vtex/sku-offers.js.map +7 -0
- package/dist/src/tools/vtex/sku-price.js +42 -0
- package/dist/src/tools/vtex/sku-price.js.map +7 -0
- package/dist/src/tools/vtex/update-inventory.js +43 -0
- package/dist/src/tools/vtex/update-inventory.js.map +7 -0
- package/dist/src/tools/vtex/update-lead-time.js +32 -0
- package/dist/src/tools/vtex/update-lead-time.js.map +7 -0
- package/dist/src/tools/vtex/warehouse-inventory.js +42 -0
- package/dist/src/tools/vtex/warehouse-inventory.js.map +7 -0
- package/dist/src/utils/case-conversion.js +21 -0
- package/dist/src/utils/case-conversion.js.map +7 -0
- package/dist/src/utils/currency.js +52 -0
- package/dist/src/utils/currency.js.map +7 -0
- package/dist/src/utils/format-order-details.js +137 -0
- package/dist/src/utils/format-order-details.js.map +7 -0
- package/dist/src/utils/google-ads.js +78 -0
- package/dist/src/utils/google-ads.js.map +7 -0
- package/dist/src/utils/money.js +83 -0
- package/dist/src/utils/money.js.map +7 -0
- package/dist/src/utils/order-status.js +11 -0
- package/dist/src/utils/order-status.js.map +7 -0
- package/dist/src/utils/pagination.js +45 -0
- package/dist/src/utils/pagination.js.map +7 -0
- package/dist/src/utils/strip-payload.js +40 -0
- package/dist/src/utils/strip-payload.js.map +7 -0
- package/dist/src/utils/type-guards.js +7 -0
- package/dist/src/utils/type-guards.js.map +7 -0
- package/package.json +66 -0
- package/public/favicon.ico +0 -0
- package/public/icon.svg +6 -0
|
@@ -0,0 +1,174 @@
|
|
|
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 googleAdsScalingHealthSchema = z.object({
|
|
17
|
+
startDate: z.string().regex(dateRegex).describe("Start date in YYYY-MM-DD format."),
|
|
18
|
+
endDate: z.string().regex(dateRegex).describe("End date in YYYY-MM-DD format."),
|
|
19
|
+
customerId: z.string().describe("Google Ads customer ID to query. Accepts digits with or without hyphens."),
|
|
20
|
+
loginCustomerId: z.string().optional().describe(
|
|
21
|
+
"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
|
|
22
|
+
),
|
|
23
|
+
breakEvenRoas: z.number().positive().describe("Break-even ROAS for the business."),
|
|
24
|
+
targetCpa: z.number().positive().optional().describe("Optional maximum acceptable CPA for the business.")
|
|
25
|
+
});
|
|
26
|
+
async function googleAdsScalingHealthHandler(params) {
|
|
27
|
+
try {
|
|
28
|
+
const customerId = resolveGoogleAdsCustomerId(params.customerId);
|
|
29
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
30
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
31
|
+
const overviewResult = await searchGoogleAds(
|
|
32
|
+
customerId,
|
|
33
|
+
[
|
|
34
|
+
"SELECT",
|
|
35
|
+
"customer.descriptive_name,",
|
|
36
|
+
"customer.currency_code,",
|
|
37
|
+
"metrics.clicks,",
|
|
38
|
+
"metrics.cost_micros,",
|
|
39
|
+
"metrics.conversions,",
|
|
40
|
+
"metrics.conversions_value",
|
|
41
|
+
"FROM customer",
|
|
42
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`
|
|
43
|
+
].join(" "),
|
|
44
|
+
developerToken,
|
|
45
|
+
loginCustomerId
|
|
46
|
+
);
|
|
47
|
+
const campaignsResult = await searchGoogleAds(
|
|
48
|
+
customerId,
|
|
49
|
+
[
|
|
50
|
+
"SELECT",
|
|
51
|
+
"campaign.name,",
|
|
52
|
+
"metrics.cost_micros,",
|
|
53
|
+
"metrics.conversions_value,",
|
|
54
|
+
"metrics.conversions",
|
|
55
|
+
"FROM campaign",
|
|
56
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
|
|
57
|
+
"AND campaign.status = 'ENABLED'",
|
|
58
|
+
"ORDER BY metrics.cost_micros DESC"
|
|
59
|
+
].join(" "),
|
|
60
|
+
developerToken,
|
|
61
|
+
loginCustomerId
|
|
62
|
+
);
|
|
63
|
+
const seriesResult = await searchGoogleAds(
|
|
64
|
+
customerId,
|
|
65
|
+
[
|
|
66
|
+
"SELECT",
|
|
67
|
+
"segments.date,",
|
|
68
|
+
"metrics.cost_micros,",
|
|
69
|
+
"metrics.conversions_value,",
|
|
70
|
+
"metrics.conversions",
|
|
71
|
+
"FROM customer",
|
|
72
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
|
|
73
|
+
"ORDER BY segments.date"
|
|
74
|
+
].join(" "),
|
|
75
|
+
developerToken,
|
|
76
|
+
loginCustomerId
|
|
77
|
+
);
|
|
78
|
+
const overviewRow = overviewResult.rows[0] ?? {};
|
|
79
|
+
const cost = microsToCurrency(getFieldValue(overviewRow, "metrics.costMicros"));
|
|
80
|
+
const conversions = round(
|
|
81
|
+
parseGoogleAdsMetricValue(getFieldValue(overviewRow, "metrics.conversions"))
|
|
82
|
+
);
|
|
83
|
+
const conversionValue = round(
|
|
84
|
+
parseGoogleAdsMetricValue(getFieldValue(overviewRow, "metrics.conversionsValue"))
|
|
85
|
+
);
|
|
86
|
+
const clicks = parseGoogleAdsMetricValue(getFieldValue(overviewRow, "metrics.clicks"));
|
|
87
|
+
const currentRoas = round(conversionValue / cost);
|
|
88
|
+
const currentCpa = round(cost / conversions);
|
|
89
|
+
const campaignCosts = campaignsResult.rows.map(
|
|
90
|
+
(row) => microsToCurrency(getFieldValue(row, "metrics.costMicros"))
|
|
91
|
+
);
|
|
92
|
+
const topCampaignCostShare = round(toPercent(campaignCosts[0] ?? 0, cost));
|
|
93
|
+
const series = seriesResult.rows.map((row) => ({
|
|
94
|
+
cost: microsToCurrency(getFieldValue(row, "metrics.costMicros")),
|
|
95
|
+
conversion_value: round(
|
|
96
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
|
|
97
|
+
),
|
|
98
|
+
conversions: round(parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions")))
|
|
99
|
+
}));
|
|
100
|
+
const midpoint = Math.ceil(series.length / 2);
|
|
101
|
+
const firstHalf = series.slice(0, midpoint);
|
|
102
|
+
const secondHalf = series.slice(midpoint);
|
|
103
|
+
const firstHalfRoas = round(
|
|
104
|
+
firstHalf.reduce((sum, item) => sum + item.conversion_value, 0) / firstHalf.reduce((sum, item) => sum + item.cost, 0)
|
|
105
|
+
);
|
|
106
|
+
const secondHalfRoas = round(
|
|
107
|
+
secondHalf.reduce((sum, item) => sum + item.conversion_value, 0) / secondHalf.reduce((sum, item) => sum + item.cost, 0)
|
|
108
|
+
);
|
|
109
|
+
let score = 0;
|
|
110
|
+
if (currentRoas >= params.breakEvenRoas) score += 35;
|
|
111
|
+
else if (currentRoas >= params.breakEvenRoas * 0.8) score += 20;
|
|
112
|
+
if (!params.targetCpa || currentCpa > 0 && currentCpa <= params.targetCpa) score += 20;
|
|
113
|
+
else if (params.targetCpa && currentCpa <= params.targetCpa * 1.2) score += 10;
|
|
114
|
+
if (topCampaignCostShare < 50) score += 20;
|
|
115
|
+
else if (topCampaignCostShare < 70) score += 10;
|
|
116
|
+
if (secondHalfRoas >= firstHalfRoas * 0.9) score += 15;
|
|
117
|
+
else if (secondHalfRoas >= firstHalfRoas * 0.75) score += 8;
|
|
118
|
+
if (conversions >= 10) score += 10;
|
|
119
|
+
else if (conversions >= 5) score += 5;
|
|
120
|
+
const status = score >= 75 ? "ready_to_scale" : score >= 50 ? "scale_with_caution" : "not_ready";
|
|
121
|
+
return object(
|
|
122
|
+
stripNulls({
|
|
123
|
+
customer_id: customerId,
|
|
124
|
+
login_customer_id: loginCustomerId,
|
|
125
|
+
customer_name: getFieldValue(overviewRow, "customer.descriptiveName"),
|
|
126
|
+
currency_code: getFieldValue(overviewRow, "customer.currencyCode"),
|
|
127
|
+
date_range: {
|
|
128
|
+
start_date: params.startDate,
|
|
129
|
+
end_date: params.endDate
|
|
130
|
+
},
|
|
131
|
+
inputs: {
|
|
132
|
+
break_even_roas: params.breakEvenRoas,
|
|
133
|
+
target_cpa: params.targetCpa
|
|
134
|
+
},
|
|
135
|
+
overview: {
|
|
136
|
+
cost,
|
|
137
|
+
clicks,
|
|
138
|
+
conversions,
|
|
139
|
+
conversion_value: conversionValue,
|
|
140
|
+
current_roas: currentRoas,
|
|
141
|
+
current_cpa: currentCpa,
|
|
142
|
+
top_campaign_cost_share_percent: topCampaignCostShare,
|
|
143
|
+
first_half_roas: firstHalfRoas,
|
|
144
|
+
second_half_roas: secondHalfRoas,
|
|
145
|
+
scaling_health_score: score,
|
|
146
|
+
scaling_health_status: status
|
|
147
|
+
},
|
|
148
|
+
checks: {
|
|
149
|
+
above_break_even: currentRoas >= params.breakEvenRoas,
|
|
150
|
+
cpa_within_target: params.targetCpa ? currentCpa <= params.targetCpa : void 0,
|
|
151
|
+
concentration_acceptable: topCampaignCostShare < 70,
|
|
152
|
+
recent_efficiency_stable: secondHalfRoas >= firstHalfRoas * 0.9,
|
|
153
|
+
conversion_volume_acceptable: conversions >= 5
|
|
154
|
+
},
|
|
155
|
+
interpretation: {
|
|
156
|
+
status,
|
|
157
|
+
message: status === "ready_to_scale" ? "The account shows enough efficiency and stability to test scaling." : status === "scale_with_caution" ? "The account has mixed signals. Scale only with tight monitoring." : "The account is not ready to scale without increasing risk."
|
|
158
|
+
},
|
|
159
|
+
metadata: {
|
|
160
|
+
request_ids: [overviewResult.requestId, campaignsResult.requestId, seriesResult.requestId]
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return error(
|
|
166
|
+
err instanceof Error ? err.message : "Failed to evaluate Google Ads scaling health"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
googleAdsScalingHealthHandler,
|
|
172
|
+
googleAdsScalingHealthSchema
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=scaling-health.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/scaling-health.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\nexport const googleAdsScalingHealthSchema = 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.\"),\n targetCpa: z\n .number()\n .positive()\n .optional()\n .describe(\"Optional maximum acceptable CPA for the business.\"),\n});\n\nexport async function googleAdsScalingHealthHandler(\n params: z.infer<typeof googleAdsScalingHealthSchema>\n) {\n try {\n const customerId = resolveGoogleAdsCustomerId(params.customerId);\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n\n const overviewResult = 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 const campaignsResult = await searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"campaign.name,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions_value,\",\n \"metrics.conversions\",\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 const seriesResult = await 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 const overviewRow = overviewResult.rows[0] ?? {};\n const cost = microsToCurrency(getFieldValue(overviewRow, \"metrics.costMicros\"));\n const conversions = round(\n parseGoogleAdsMetricValue(getFieldValue(overviewRow, \"metrics.conversions\"))\n );\n const conversionValue = round(\n parseGoogleAdsMetricValue(getFieldValue(overviewRow, \"metrics.conversionsValue\"))\n );\n const clicks = parseGoogleAdsMetricValue(getFieldValue(overviewRow, \"metrics.clicks\"));\n const currentRoas = round(conversionValue / cost);\n const currentCpa = round(cost / conversions);\n\n const campaignCosts = campaignsResult.rows.map((row) =>\n microsToCurrency(getFieldValue(row, \"metrics.costMicros\"))\n );\n const topCampaignCostShare = round(toPercent(campaignCosts[0] ?? 0, cost));\n\n const series = seriesResult.rows.map((row) => ({\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 let score = 0;\n if (currentRoas >= params.breakEvenRoas) score += 35;\n else if (currentRoas >= params.breakEvenRoas * 0.8) score += 20;\n\n if (!params.targetCpa || (currentCpa > 0 && currentCpa <= params.targetCpa)) score += 20;\n else if (params.targetCpa && currentCpa <= params.targetCpa * 1.2) score += 10;\n\n if (topCampaignCostShare < 50) score += 20;\n else if (topCampaignCostShare < 70) score += 10;\n\n if (secondHalfRoas >= firstHalfRoas * 0.9) score += 15;\n else if (secondHalfRoas >= firstHalfRoas * 0.75) score += 8;\n\n if (conversions >= 10) score += 10;\n else if (conversions >= 5) score += 5;\n\n const status =\n score >= 75 ? \"ready_to_scale\" : score >= 50 ? \"scale_with_caution\" : \"not_ready\";\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer_name: getFieldValue(overviewRow, \"customer.descriptiveName\"),\n currency_code: getFieldValue(overviewRow, \"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 cost,\n clicks,\n conversions,\n conversion_value: conversionValue,\n current_roas: currentRoas,\n current_cpa: currentCpa,\n top_campaign_cost_share_percent: topCampaignCostShare,\n first_half_roas: firstHalfRoas,\n second_half_roas: secondHalfRoas,\n scaling_health_score: score,\n scaling_health_status: status,\n },\n checks: {\n above_break_even: currentRoas >= params.breakEvenRoas,\n cpa_within_target: params.targetCpa ? currentCpa <= params.targetCpa : undefined,\n concentration_acceptable: topCampaignCostShare < 70,\n recent_efficiency_stable: secondHalfRoas >= firstHalfRoas * 0.9,\n conversion_volume_acceptable: conversions >= 5,\n },\n interpretation: {\n status,\n message:\n status === \"ready_to_scale\"\n ? \"The account shows enough efficiency and stability to test scaling.\"\n : status === \"scale_with_caution\"\n ? \"The account has mixed signals. Scale only with tight monitoring.\"\n : \"The account is not ready to scale without increasing risk.\",\n },\n metadata: {\n request_ids: [overviewResult.requestId, campaignsResult.requestId, seriesResult.requestId],\n },\n })\n );\n } catch (err) {\n return error(\n err instanceof Error ? err.message : \"Failed to evaluate Google Ads scaling health\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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 googleAdsSearchTermsSummarySchema = z.object({
|
|
17
|
+
startDate: z.string().regex(dateRegex).describe("Start date in YYYY-MM-DD format."),
|
|
18
|
+
endDate: z.string().regex(dateRegex).describe("End date in YYYY-MM-DD format."),
|
|
19
|
+
customerId: z.string().describe("Google Ads customer ID to query. Accepts digits with or without hyphens."),
|
|
20
|
+
loginCustomerId: z.string().optional().describe(
|
|
21
|
+
"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
|
|
22
|
+
),
|
|
23
|
+
limit: z.number().int().min(1).max(50).optional().default(20).describe("Maximum number of search terms to return, ordered by spend descending.")
|
|
24
|
+
});
|
|
25
|
+
async function googleAdsSearchTermsSummaryHandler(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
|
+
"search_term_view.search_term,",
|
|
37
|
+
"campaign.id,",
|
|
38
|
+
"campaign.name,",
|
|
39
|
+
"ad_group.id,",
|
|
40
|
+
"ad_group.name,",
|
|
41
|
+
"metrics.impressions,",
|
|
42
|
+
"metrics.clicks,",
|
|
43
|
+
"metrics.cost_micros,",
|
|
44
|
+
"metrics.conversions,",
|
|
45
|
+
"metrics.conversions_value",
|
|
46
|
+
"FROM search_term_view",
|
|
47
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
|
|
48
|
+
"AND metrics.impressions > 0",
|
|
49
|
+
"ORDER BY metrics.cost_micros DESC",
|
|
50
|
+
`LIMIT ${params.limit}`
|
|
51
|
+
].join(" "),
|
|
52
|
+
developerToken,
|
|
53
|
+
loginCustomerId
|
|
54
|
+
);
|
|
55
|
+
const searchTerms = result.rows.map((row) => {
|
|
56
|
+
const impressions = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.impressions"));
|
|
57
|
+
const clicks = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
|
|
58
|
+
const cost = microsToCurrency(getFieldValue(row, "metrics.costMicros"));
|
|
59
|
+
const conversions = round(
|
|
60
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
|
|
61
|
+
);
|
|
62
|
+
const conversionValue = round(
|
|
63
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
|
|
64
|
+
);
|
|
65
|
+
return {
|
|
66
|
+
search_term: getFieldValue(row, "searchTermView.searchTerm"),
|
|
67
|
+
campaign_id: getFieldValue(row, "campaign.id"),
|
|
68
|
+
campaign_name: getFieldValue(row, "campaign.name"),
|
|
69
|
+
ad_group_id: getFieldValue(row, "adGroup.id"),
|
|
70
|
+
ad_group_name: getFieldValue(row, "adGroup.name"),
|
|
71
|
+
impressions,
|
|
72
|
+
clicks,
|
|
73
|
+
cost,
|
|
74
|
+
conversions,
|
|
75
|
+
conversion_value: conversionValue,
|
|
76
|
+
ctr_percent: round(toPercent(clicks, impressions)),
|
|
77
|
+
cpa: round(cost / conversions),
|
|
78
|
+
roas: round(conversionValue / cost)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const totals = searchTerms.reduce(
|
|
82
|
+
(acc, item) => {
|
|
83
|
+
acc.impressions += item.impressions;
|
|
84
|
+
acc.clicks += item.clicks;
|
|
85
|
+
acc.cost += item.cost;
|
|
86
|
+
acc.conversions += item.conversions;
|
|
87
|
+
acc.conversion_value += item.conversion_value;
|
|
88
|
+
return acc;
|
|
89
|
+
},
|
|
90
|
+
{ impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }
|
|
91
|
+
);
|
|
92
|
+
return object(
|
|
93
|
+
stripNulls({
|
|
94
|
+
customer_id: customerId,
|
|
95
|
+
login_customer_id: loginCustomerId,
|
|
96
|
+
customer_name: getFieldValue(result.rows[0] ?? {}, "customer.descriptiveName"),
|
|
97
|
+
currency_code: getFieldValue(result.rows[0] ?? {}, "customer.currencyCode"),
|
|
98
|
+
date_range: {
|
|
99
|
+
start_date: params.startDate,
|
|
100
|
+
end_date: params.endDate
|
|
101
|
+
},
|
|
102
|
+
overview: {
|
|
103
|
+
returned_terms: searchTerms.length,
|
|
104
|
+
impressions: totals.impressions,
|
|
105
|
+
clicks: totals.clicks,
|
|
106
|
+
cost: round(totals.cost),
|
|
107
|
+
conversions: round(totals.conversions),
|
|
108
|
+
conversion_value: round(totals.conversion_value),
|
|
109
|
+
ctr_percent: round(toPercent(totals.clicks, totals.impressions)),
|
|
110
|
+
cpa: round(totals.cost / totals.conversions),
|
|
111
|
+
roas: round(totals.conversion_value / totals.cost),
|
|
112
|
+
top_term_by_cost: searchTerms[0]?.search_term
|
|
113
|
+
},
|
|
114
|
+
search_terms: searchTerms,
|
|
115
|
+
metadata: {
|
|
116
|
+
row_count: result.rows.length,
|
|
117
|
+
request_id: result.requestId
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return error(
|
|
123
|
+
err instanceof Error ? err.message : "Failed to fetch Google Ads search terms summary"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
googleAdsSearchTermsSummaryHandler,
|
|
129
|
+
googleAdsSearchTermsSummarySchema
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=search-terms-summary.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/search-terms-summary.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\nexport const googleAdsSearchTermsSummarySchema = 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 limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .default(20)\n .describe(\"Maximum number of search terms to return, ordered by spend descending.\"),\n});\n\nexport async function googleAdsSearchTermsSummaryHandler(\n params: z.infer<typeof googleAdsSearchTermsSummarySchema>\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 \"search_term_view.search_term,\",\n \"campaign.id,\",\n \"campaign.name,\",\n \"ad_group.id,\",\n \"ad_group.name,\",\n \"metrics.impressions,\",\n \"metrics.clicks,\",\n \"metrics.cost_micros,\",\n \"metrics.conversions,\",\n \"metrics.conversions_value\",\n \"FROM search_term_view\",\n `WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,\n \"AND metrics.impressions > 0\",\n \"ORDER BY metrics.cost_micros DESC\",\n `LIMIT ${params.limit}`,\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const searchTerms = 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 search_term: getFieldValue(row, \"searchTermView.searchTerm\"),\n campaign_id: getFieldValue(row, \"campaign.id\"),\n campaign_name: getFieldValue(row, \"campaign.name\"),\n ad_group_id: getFieldValue(row, \"adGroup.id\"),\n ad_group_name: getFieldValue(row, \"adGroup.name\"),\n impressions,\n clicks,\n cost,\n conversions,\n conversion_value: conversionValue,\n ctr_percent: round(toPercent(clicks, impressions)),\n cpa: round(cost / conversions),\n roas: round(conversionValue / cost),\n };\n });\n\n const totals = searchTerms.reduce(\n (acc, item) => {\n acc.impressions += item.impressions;\n acc.clicks += item.clicks;\n acc.cost += item.cost;\n acc.conversions += item.conversions;\n acc.conversion_value += item.conversion_value;\n return acc;\n },\n { impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }\n );\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer_name: getFieldValue(result.rows[0] ?? {}, \"customer.descriptiveName\"),\n currency_code: getFieldValue(result.rows[0] ?? {}, \"customer.currencyCode\"),\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n overview: {\n returned_terms: searchTerms.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 cpa: round(totals.cost / totals.conversions),\n roas: round(totals.conversion_value / totals.cost),\n top_term_by_cost: searchTerms[0]?.search_term,\n },\n search_terms: searchTerms,\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 search terms summary\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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 googleAdsTimeSeriesGranularityEnum = z.enum(["daily", "weekly"]);
|
|
17
|
+
const googleAdsTimeSeriesSchema = 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
|
+
granularity: googleAdsTimeSeriesGranularityEnum.optional().default("daily").describe("Time series granularity: daily or weekly.")
|
|
25
|
+
});
|
|
26
|
+
async function googleAdsTimeSeriesHandler(params) {
|
|
27
|
+
try {
|
|
28
|
+
const customerId = resolveGoogleAdsCustomerId(params.customerId);
|
|
29
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
30
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
31
|
+
const segmentField = params.granularity === "weekly" ? "segments.week" : "segments.date";
|
|
32
|
+
const result = await searchGoogleAds(
|
|
33
|
+
customerId,
|
|
34
|
+
[
|
|
35
|
+
"SELECT",
|
|
36
|
+
"customer.descriptive_name,",
|
|
37
|
+
"customer.currency_code,",
|
|
38
|
+
`${segmentField},`,
|
|
39
|
+
"metrics.impressions,",
|
|
40
|
+
"metrics.clicks,",
|
|
41
|
+
"metrics.cost_micros,",
|
|
42
|
+
"metrics.conversions,",
|
|
43
|
+
"metrics.conversions_value",
|
|
44
|
+
"FROM customer",
|
|
45
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
|
|
46
|
+
`ORDER BY ${segmentField}`
|
|
47
|
+
].join(" "),
|
|
48
|
+
developerToken,
|
|
49
|
+
loginCustomerId
|
|
50
|
+
);
|
|
51
|
+
const series = result.rows.map((row) => {
|
|
52
|
+
const impressions = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.impressions"));
|
|
53
|
+
const clicks = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
|
|
54
|
+
const cost = microsToCurrency(getFieldValue(row, "metrics.costMicros"));
|
|
55
|
+
const conversions = round(
|
|
56
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
|
|
57
|
+
);
|
|
58
|
+
const conversionValue = round(
|
|
59
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
period_start: String(
|
|
63
|
+
getFieldValue(
|
|
64
|
+
row,
|
|
65
|
+
params.granularity === "weekly" ? "segments.week" : "segments.date"
|
|
66
|
+
) ?? ""
|
|
67
|
+
),
|
|
68
|
+
impressions,
|
|
69
|
+
clicks,
|
|
70
|
+
cost,
|
|
71
|
+
conversions,
|
|
72
|
+
conversion_value: conversionValue,
|
|
73
|
+
ctr_percent: round(toPercent(clicks, impressions)),
|
|
74
|
+
cpa: round(cost / conversions),
|
|
75
|
+
roas: round(conversionValue / cost)
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const totals = series.reduce(
|
|
79
|
+
(acc, item) => {
|
|
80
|
+
acc.impressions += item.impressions;
|
|
81
|
+
acc.clicks += item.clicks;
|
|
82
|
+
acc.cost += item.cost;
|
|
83
|
+
acc.conversions += item.conversions;
|
|
84
|
+
acc.conversion_value += item.conversion_value;
|
|
85
|
+
return acc;
|
|
86
|
+
},
|
|
87
|
+
{ impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }
|
|
88
|
+
);
|
|
89
|
+
return object(
|
|
90
|
+
stripNulls({
|
|
91
|
+
customer_id: customerId,
|
|
92
|
+
login_customer_id: loginCustomerId,
|
|
93
|
+
customer_name: getFieldValue(result.rows[0] ?? {}, "customer.descriptiveName"),
|
|
94
|
+
currency_code: getFieldValue(result.rows[0] ?? {}, "customer.currencyCode"),
|
|
95
|
+
date_range: {
|
|
96
|
+
start_date: params.startDate,
|
|
97
|
+
end_date: params.endDate
|
|
98
|
+
},
|
|
99
|
+
granularity: params.granularity,
|
|
100
|
+
overview: {
|
|
101
|
+
periods: series.length,
|
|
102
|
+
impressions: totals.impressions,
|
|
103
|
+
clicks: totals.clicks,
|
|
104
|
+
cost: round(totals.cost),
|
|
105
|
+
conversions: round(totals.conversions),
|
|
106
|
+
conversion_value: round(totals.conversion_value),
|
|
107
|
+
ctr_percent: round(toPercent(totals.clicks, totals.impressions)),
|
|
108
|
+
cpa: round(totals.cost / totals.conversions),
|
|
109
|
+
roas: round(totals.conversion_value / totals.cost)
|
|
110
|
+
},
|
|
111
|
+
series,
|
|
112
|
+
metadata: {
|
|
113
|
+
row_count: result.rows.length,
|
|
114
|
+
request_id: result.requestId
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return error(err instanceof Error ? err.message : "Failed to fetch Google Ads time series");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
googleAdsTimeSeriesHandler,
|
|
124
|
+
googleAdsTimeSeriesSchema
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=time-series.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/time-series.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 googleAdsTimeSeriesGranularityEnum = z.enum([\"daily\", \"weekly\"]);\n\nexport const googleAdsTimeSeriesSchema = 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 granularity: googleAdsTimeSeriesGranularityEnum\n .optional()\n .default(\"daily\")\n .describe(\"Time series granularity: daily or weekly.\"),\n});\n\nexport async function googleAdsTimeSeriesHandler(\n params: z.infer<typeof googleAdsTimeSeriesSchema>\n) {\n try {\n const customerId = resolveGoogleAdsCustomerId(params.customerId);\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n const segmentField = params.granularity === \"weekly\" ? \"segments.week\" : \"segments.date\";\n\n const result = await searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"customer.descriptive_name,\",\n \"customer.currency_code,\",\n `${segmentField},`,\n \"metrics.impressions,\",\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 `ORDER BY ${segmentField}`,\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const series = 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 period_start: String(\n getFieldValue(\n row,\n params.granularity === \"weekly\" ? \"segments.week\" : \"segments.date\"\n ) ?? \"\"\n ),\n impressions,\n clicks,\n cost,\n conversions,\n conversion_value: conversionValue,\n ctr_percent: round(toPercent(clicks, impressions)),\n cpa: round(cost / conversions),\n roas: round(conversionValue / cost),\n };\n });\n\n const totals = series.reduce(\n (acc, item) => {\n acc.impressions += item.impressions;\n acc.clicks += item.clicks;\n acc.cost += item.cost;\n acc.conversions += item.conversions;\n acc.conversion_value += item.conversion_value;\n return acc;\n },\n { impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }\n );\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer_name: getFieldValue(result.rows[0] ?? {}, \"customer.descriptiveName\"),\n currency_code: getFieldValue(result.rows[0] ?? {}, \"customer.currencyCode\"),\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n granularity: params.granularity,\n overview: {\n periods: series.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 cpa: round(totals.cost / totals.conversions),\n roas: round(totals.conversion_value / totals.cost),\n },\n series,\n metadata: {\n row_count: result.rows.length,\n request_id: result.requestId,\n },\n })\n );\n } catch (err) {\n return error(err instanceof Error ? err.message : \"Failed to fetch Google Ads time series\");\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/tools/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./vtex/index.js\";\nexport * from \"./analytics/index.js\";\nexport * from \"./google-ads/index.js\";\nexport * from \"./search-console/index.js\";\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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 searchConsoleCountryBreakdownSchema = 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 searchConsoleCountryBreakdownHandler(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: ["country"],
|
|
51
|
+
searchType: params.searchType,
|
|
52
|
+
dimensionFilters
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, ["country"]));
|
|
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
|
+
country_breakdown: rows.map((row) => ({
|
|
65
|
+
country: row.dimensions.country,
|
|
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 country breakdown"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
searchConsoleCountryBreakdownHandler,
|
|
83
|
+
searchConsoleCountryBreakdownSchema
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=country-breakdown.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/search-console/country-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 searchConsoleCountryBreakdownSchema = 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 searchConsoleCountryBreakdownHandler(\n params: z.infer<typeof searchConsoleCountryBreakdownSchema>\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: [\"country\"],\n searchType: params.searchType,\n dimensionFilters,\n })\n );\n\n const rows = (response.rows ?? []).map((row) => normalizeSearchConsoleRow(row, [\"country\"]));\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 country_breakdown: rows.map((row) => ({\n country: row.dimensions.country,\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 country 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,sCAAsC,EAAE,OAAO;AAAA,EAC1D,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,qCACpB,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,SAAS;AAAA,QACtB,YAAY,OAAO;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,0BAA0B,KAAK,CAAC,SAAS,CAAC,CAAC;AAC3F,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,mBAAmB,KAAK,IAAI,CAAC,SAAS;AAAA,UACpC,SAAS,IAAI,WAAW;AAAA,UACxB,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
|
+
}
|