@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,129 @@
|
|
|
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 googleAdsChannelMixSchema = 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
|
+
});
|
|
24
|
+
async function googleAdsChannelMixHandler(params) {
|
|
25
|
+
try {
|
|
26
|
+
const customerId = resolveGoogleAdsCustomerId(params.customerId);
|
|
27
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
28
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
29
|
+
const result = await searchGoogleAds(
|
|
30
|
+
customerId,
|
|
31
|
+
[
|
|
32
|
+
"SELECT",
|
|
33
|
+
"customer.descriptive_name,",
|
|
34
|
+
"customer.currency_code,",
|
|
35
|
+
"campaign.advertising_channel_type,",
|
|
36
|
+
"metrics.impressions,",
|
|
37
|
+
"metrics.clicks,",
|
|
38
|
+
"metrics.cost_micros,",
|
|
39
|
+
"metrics.conversions,",
|
|
40
|
+
"metrics.conversions_value",
|
|
41
|
+
"FROM campaign",
|
|
42
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`,
|
|
43
|
+
"AND campaign.status = 'ENABLED'",
|
|
44
|
+
"ORDER BY metrics.cost_micros DESC"
|
|
45
|
+
].join(" "),
|
|
46
|
+
developerToken,
|
|
47
|
+
loginCustomerId
|
|
48
|
+
);
|
|
49
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const row of result.rows) {
|
|
51
|
+
const channelType = String(
|
|
52
|
+
getFieldValue(row, "campaign.advertisingChannelType", "UNKNOWN") ?? "UNKNOWN"
|
|
53
|
+
);
|
|
54
|
+
const current = grouped.get(channelType) ?? {
|
|
55
|
+
channel_type: channelType,
|
|
56
|
+
impressions: 0,
|
|
57
|
+
clicks: 0,
|
|
58
|
+
cost: 0,
|
|
59
|
+
conversions: 0,
|
|
60
|
+
conversion_value: 0
|
|
61
|
+
};
|
|
62
|
+
current.impressions += parseGoogleAdsMetricValue(getFieldValue(row, "metrics.impressions"));
|
|
63
|
+
current.clicks += parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
|
|
64
|
+
current.cost += microsToCurrency(getFieldValue(row, "metrics.costMicros"));
|
|
65
|
+
current.conversions += round(
|
|
66
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
|
|
67
|
+
);
|
|
68
|
+
current.conversion_value += round(
|
|
69
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
|
|
70
|
+
);
|
|
71
|
+
grouped.set(channelType, current);
|
|
72
|
+
}
|
|
73
|
+
const totals = Array.from(grouped.values()).reduce(
|
|
74
|
+
(acc, channel) => {
|
|
75
|
+
acc.impressions += channel.impressions;
|
|
76
|
+
acc.clicks += channel.clicks;
|
|
77
|
+
acc.cost += channel.cost;
|
|
78
|
+
acc.conversions += channel.conversions;
|
|
79
|
+
acc.conversion_value += channel.conversion_value;
|
|
80
|
+
return acc;
|
|
81
|
+
},
|
|
82
|
+
{ impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }
|
|
83
|
+
);
|
|
84
|
+
const channels = Array.from(grouped.values()).map((channel) => ({
|
|
85
|
+
...channel,
|
|
86
|
+
cost: round(channel.cost),
|
|
87
|
+
conversions: round(channel.conversions),
|
|
88
|
+
conversion_value: round(channel.conversion_value),
|
|
89
|
+
ctr_percent: round(toPercent(channel.clicks, channel.impressions)),
|
|
90
|
+
cost_share_percent: round(toPercent(channel.cost, totals.cost)),
|
|
91
|
+
cpa: round(channel.cost / channel.conversions),
|
|
92
|
+
roas: round(channel.conversion_value / channel.cost)
|
|
93
|
+
})).sort((a, b) => b.cost - a.cost);
|
|
94
|
+
return object(
|
|
95
|
+
stripNulls({
|
|
96
|
+
customer_id: customerId,
|
|
97
|
+
login_customer_id: loginCustomerId,
|
|
98
|
+
customer_name: getFieldValue(result.rows[0] ?? {}, "customer.descriptiveName"),
|
|
99
|
+
currency_code: getFieldValue(result.rows[0] ?? {}, "customer.currencyCode"),
|
|
100
|
+
date_range: {
|
|
101
|
+
start_date: params.startDate,
|
|
102
|
+
end_date: params.endDate
|
|
103
|
+
},
|
|
104
|
+
overview: {
|
|
105
|
+
total_channels: channels.length,
|
|
106
|
+
impressions: totals.impressions,
|
|
107
|
+
clicks: totals.clicks,
|
|
108
|
+
cost: round(totals.cost),
|
|
109
|
+
conversions: round(totals.conversions),
|
|
110
|
+
conversion_value: round(totals.conversion_value),
|
|
111
|
+
dominant_channel: channels[0]?.channel_type,
|
|
112
|
+
dominant_channel_cost_share_percent: channels[0]?.cost_share_percent
|
|
113
|
+
},
|
|
114
|
+
channels,
|
|
115
|
+
metadata: {
|
|
116
|
+
row_count: result.rows.length,
|
|
117
|
+
request_id: result.requestId
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return error(err instanceof Error ? err.message : "Failed to fetch Google Ads channel mix");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
googleAdsChannelMixHandler,
|
|
127
|
+
googleAdsChannelMixSchema
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=channel-mix.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/channel-mix.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 googleAdsChannelMixSchema = 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 googleAdsChannelMixHandler(\n params: z.infer<typeof googleAdsChannelMixSchema>\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 \"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\n const grouped = new Map<\n string,\n {\n channel_type: string;\n impressions: number;\n clicks: number;\n cost: number;\n conversions: number;\n conversion_value: number;\n }\n >();\n\n for (const row of result.rows) {\n const channelType = String(\n getFieldValue(row, \"campaign.advertisingChannelType\", \"UNKNOWN\") ?? \"UNKNOWN\"\n );\n const current = grouped.get(channelType) ?? {\n channel_type: channelType,\n impressions: 0,\n clicks: 0,\n cost: 0,\n conversions: 0,\n conversion_value: 0,\n };\n\n current.impressions += parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.impressions\"));\n current.clicks += parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.clicks\"));\n current.cost += microsToCurrency(getFieldValue(row, \"metrics.costMicros\"));\n current.conversions += round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversions\"))\n );\n current.conversion_value += round(\n parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.conversionsValue\"))\n );\n\n grouped.set(channelType, current);\n }\n\n const totals = Array.from(grouped.values()).reduce(\n (acc, channel) => {\n acc.impressions += channel.impressions;\n acc.clicks += channel.clicks;\n acc.cost += channel.cost;\n acc.conversions += channel.conversions;\n acc.conversion_value += channel.conversion_value;\n return acc;\n },\n { impressions: 0, clicks: 0, cost: 0, conversions: 0, conversion_value: 0 }\n );\n\n const channels = Array.from(grouped.values())\n .map((channel) => ({\n ...channel,\n cost: round(channel.cost),\n conversions: round(channel.conversions),\n conversion_value: round(channel.conversion_value),\n ctr_percent: round(toPercent(channel.clicks, channel.impressions)),\n cost_share_percent: round(toPercent(channel.cost, totals.cost)),\n cpa: round(channel.cost / channel.conversions),\n roas: round(channel.conversion_value / channel.cost),\n }))\n .sort((a, b) => b.cost - a.cost);\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 total_channels: channels.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 dominant_channel: channels[0]?.channel_type,\n dominant_channel_cost_share_percent: channels[0]?.cost_share_percent,\n },\n channels,\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 channel mix\");\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
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 googleAdsCompareAccountsSchema = 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
|
+
customerIds: z.array(z.string()).min(2).max(10).describe("Google Ads customer IDs to compare. 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
|
+
});
|
|
24
|
+
async function googleAdsCompareAccountsHandler(params) {
|
|
25
|
+
try {
|
|
26
|
+
const customerIds = params.customerIds.map((id) => resolveGoogleAdsCustomerId(id));
|
|
27
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
28
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
29
|
+
const accountResults = await Promise.all(
|
|
30
|
+
customerIds.map(async (customerId) => {
|
|
31
|
+
const [overviewResult, campaignResult] = await Promise.all([
|
|
32
|
+
searchGoogleAds(
|
|
33
|
+
customerId,
|
|
34
|
+
[
|
|
35
|
+
"SELECT",
|
|
36
|
+
"customer.descriptive_name,",
|
|
37
|
+
"customer.currency_code,",
|
|
38
|
+
"metrics.clicks,",
|
|
39
|
+
"metrics.cost_micros,",
|
|
40
|
+
"metrics.conversions,",
|
|
41
|
+
"metrics.conversions_value",
|
|
42
|
+
"FROM customer",
|
|
43
|
+
`WHERE segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`
|
|
44
|
+
].join(" "),
|
|
45
|
+
developerToken,
|
|
46
|
+
loginCustomerId
|
|
47
|
+
),
|
|
48
|
+
searchGoogleAds(
|
|
49
|
+
customerId,
|
|
50
|
+
[
|
|
51
|
+
"SELECT",
|
|
52
|
+
"metrics.cost_micros",
|
|
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
|
+
]);
|
|
62
|
+
const row = overviewResult.rows[0] ?? {};
|
|
63
|
+
const cost = microsToCurrency(getFieldValue(row, "metrics.costMicros"));
|
|
64
|
+
const conversions = round(
|
|
65
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversions"))
|
|
66
|
+
);
|
|
67
|
+
const conversionValue = round(
|
|
68
|
+
parseGoogleAdsMetricValue(getFieldValue(row, "metrics.conversionsValue"))
|
|
69
|
+
);
|
|
70
|
+
const clicks = parseGoogleAdsMetricValue(getFieldValue(row, "metrics.clicks"));
|
|
71
|
+
const roas = round(conversionValue / cost);
|
|
72
|
+
const cpa = round(cost / conversions);
|
|
73
|
+
const topCampaignCost = microsToCurrency(
|
|
74
|
+
getFieldValue(campaignResult.rows[0] ?? {}, "metrics.costMicros")
|
|
75
|
+
);
|
|
76
|
+
const topCampaignCostSharePercent = round(toPercent(topCampaignCost, cost));
|
|
77
|
+
const riskScore = (roas < 1 ? 40 : roas < 1.5 ? 20 : 0) + (conversions < 5 ? 25 : 0) + (topCampaignCostSharePercent >= 70 ? 25 : topCampaignCostSharePercent >= 50 ? 10 : 0) + (cost <= 0 ? 10 : 0);
|
|
78
|
+
return {
|
|
79
|
+
customer_id: customerId,
|
|
80
|
+
customer_name: getFieldValue(row, "customer.descriptiveName"),
|
|
81
|
+
currency_code: getFieldValue(row, "customer.currencyCode"),
|
|
82
|
+
cost,
|
|
83
|
+
clicks,
|
|
84
|
+
conversions,
|
|
85
|
+
conversion_value: conversionValue,
|
|
86
|
+
roas,
|
|
87
|
+
cpa,
|
|
88
|
+
top_campaign_cost_share_percent: topCampaignCostSharePercent,
|
|
89
|
+
risk_score: riskScore
|
|
90
|
+
};
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
const rankedAccounts = accountResults.sort((a, b) => b.risk_score - a.risk_score);
|
|
94
|
+
return object(
|
|
95
|
+
stripNulls({
|
|
96
|
+
login_customer_id: loginCustomerId,
|
|
97
|
+
date_range: {
|
|
98
|
+
start_date: params.startDate,
|
|
99
|
+
end_date: params.endDate
|
|
100
|
+
},
|
|
101
|
+
overview: {
|
|
102
|
+
compared_accounts: rankedAccounts.length,
|
|
103
|
+
highest_risk_customer_id: rankedAccounts[0]?.customer_id,
|
|
104
|
+
highest_risk_customer_name: rankedAccounts[0]?.customer_name,
|
|
105
|
+
lowest_risk_customer_id: rankedAccounts.at(-1)?.customer_id,
|
|
106
|
+
lowest_risk_customer_name: rankedAccounts.at(-1)?.customer_name
|
|
107
|
+
},
|
|
108
|
+
accounts: rankedAccounts,
|
|
109
|
+
interpretation: {
|
|
110
|
+
worst_account: rankedAccounts[0] ? `${rankedAccounts[0].customer_name} appears to be the weakest account, mainly driven by ROAS ${rankedAccounts[0].roas}, ${rankedAccounts[0].conversions} conversions, and ${rankedAccounts[0].top_campaign_cost_share_percent}% spend concentration in the top campaign.` : void 0
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return error(err instanceof Error ? err.message : "Failed to compare Google Ads accounts");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
googleAdsCompareAccountsHandler,
|
|
120
|
+
googleAdsCompareAccountsSchema
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=compare-accounts.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/compare-accounts.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 googleAdsCompareAccountsSchema = 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 customerIds: z\n .array(z.string())\n .min(2)\n .max(10)\n .describe(\"Google Ads customer IDs to compare. 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 googleAdsCompareAccountsHandler(\n params: z.infer<typeof googleAdsCompareAccountsSchema>\n) {\n try {\n const customerIds = params.customerIds.map((id) => resolveGoogleAdsCustomerId(id));\n const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);\n const developerToken = resolveGoogleAdsDeveloperToken();\n\n const accountResults = await Promise.all(\n customerIds.map(async (customerId) => {\n const [overviewResult, campaignResult] = await Promise.all([\n 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 searchGoogleAds(\n customerId,\n [\n \"SELECT\",\n \"metrics.cost_micros\",\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 ]);\n\n const row = overviewResult.rows[0] ?? {};\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 clicks = parseGoogleAdsMetricValue(getFieldValue(row, \"metrics.clicks\"));\n const roas = round(conversionValue / cost);\n const cpa = round(cost / conversions);\n const topCampaignCost = microsToCurrency(\n getFieldValue(campaignResult.rows[0] ?? {}, \"metrics.costMicros\")\n );\n const topCampaignCostSharePercent = round(toPercent(topCampaignCost, cost));\n\n const riskScore =\n (roas < 1 ? 40 : roas < 1.5 ? 20 : 0) +\n (conversions < 5 ? 25 : 0) +\n (topCampaignCostSharePercent >= 70 ? 25 : topCampaignCostSharePercent >= 50 ? 10 : 0) +\n (cost <= 0 ? 10 : 0);\n\n return {\n customer_id: customerId,\n customer_name: getFieldValue(row, \"customer.descriptiveName\"),\n currency_code: getFieldValue(row, \"customer.currencyCode\"),\n cost,\n clicks,\n conversions,\n conversion_value: conversionValue,\n roas,\n cpa,\n top_campaign_cost_share_percent: topCampaignCostSharePercent,\n risk_score: riskScore,\n };\n })\n );\n\n const rankedAccounts = accountResults.sort((a, b) => b.risk_score - a.risk_score);\n\n return object(\n stripNulls({\n login_customer_id: loginCustomerId,\n date_range: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n overview: {\n compared_accounts: rankedAccounts.length,\n highest_risk_customer_id: rankedAccounts[0]?.customer_id,\n highest_risk_customer_name: rankedAccounts[0]?.customer_name,\n lowest_risk_customer_id: rankedAccounts.at(-1)?.customer_id,\n lowest_risk_customer_name: rankedAccounts.at(-1)?.customer_name,\n },\n accounts: rankedAccounts,\n interpretation: {\n worst_account: rankedAccounts[0]\n ? `${rankedAccounts[0].customer_name} appears to be the weakest account, mainly driven by ROAS ${rankedAccounts[0].roas}, ${rankedAccounts[0].conversions} conversions, and ${rankedAccounts[0].top_campaign_cost_share_percent}% spend concentration in the top campaign.`\n : undefined,\n },\n })\n );\n } catch (err) {\n return error(err instanceof Error ? err.message : \"Failed to compare Google Ads accounts\");\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
getFieldValue,
|
|
5
|
+
resolveGoogleAdsCustomerId,
|
|
6
|
+
resolveGoogleAdsDeveloperToken,
|
|
7
|
+
resolveGoogleAdsLoginCustomerId
|
|
8
|
+
} from "../../utils/google-ads.js";
|
|
9
|
+
import { searchGoogleAds } from "../../services/google-ads/google-ads-client.js";
|
|
10
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
11
|
+
const googleAdsCustomerClientsSchema = z.object({
|
|
12
|
+
customerId: z.string().describe("Google Ads customer ID to inspect for visible child accounts. Usually the MCC ID."),
|
|
13
|
+
loginCustomerId: z.string().optional().describe(
|
|
14
|
+
"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
|
|
15
|
+
)
|
|
16
|
+
});
|
|
17
|
+
async function googleAdsCustomerClientsHandler(params) {
|
|
18
|
+
try {
|
|
19
|
+
const customerId = resolveGoogleAdsCustomerId(params.customerId);
|
|
20
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
21
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
22
|
+
const result = await searchGoogleAds(
|
|
23
|
+
customerId,
|
|
24
|
+
[
|
|
25
|
+
"SELECT",
|
|
26
|
+
"customer_client.client_customer,",
|
|
27
|
+
"customer_client.descriptive_name,",
|
|
28
|
+
"customer_client.currency_code,",
|
|
29
|
+
"customer_client.time_zone,",
|
|
30
|
+
"customer_client.manager,",
|
|
31
|
+
"customer_client.test_account,",
|
|
32
|
+
"customer_client.status",
|
|
33
|
+
"FROM customer_client",
|
|
34
|
+
"ORDER BY customer_client.descriptive_name"
|
|
35
|
+
].join(" "),
|
|
36
|
+
developerToken,
|
|
37
|
+
loginCustomerId
|
|
38
|
+
);
|
|
39
|
+
const customers = result.rows.map((row) => ({
|
|
40
|
+
customer_id: String(getFieldValue(row, "customerClient.clientCustomer") ?? "").replace(
|
|
41
|
+
"customers/",
|
|
42
|
+
""
|
|
43
|
+
),
|
|
44
|
+
descriptive_name: getFieldValue(row, "customerClient.descriptiveName"),
|
|
45
|
+
currency_code: getFieldValue(row, "customerClient.currencyCode"),
|
|
46
|
+
time_zone: getFieldValue(row, "customerClient.timeZone"),
|
|
47
|
+
is_manager: getFieldValue(row, "customerClient.manager"),
|
|
48
|
+
is_test_account: getFieldValue(row, "customerClient.testAccount"),
|
|
49
|
+
status: getFieldValue(row, "customerClient.status")
|
|
50
|
+
}));
|
|
51
|
+
return object(
|
|
52
|
+
stripNulls({
|
|
53
|
+
customer_id: customerId,
|
|
54
|
+
login_customer_id: loginCustomerId,
|
|
55
|
+
overview: {
|
|
56
|
+
total_visible_customers: customers.length,
|
|
57
|
+
manager_customers: customers.filter((customer) => customer.is_manager).length,
|
|
58
|
+
client_customers: customers.filter((customer) => customer.is_manager === false).length
|
|
59
|
+
},
|
|
60
|
+
customers,
|
|
61
|
+
metadata: {
|
|
62
|
+
row_count: result.rows.length,
|
|
63
|
+
request_id: result.requestId
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return error(
|
|
69
|
+
err instanceof Error ? err.message : "Failed to list visible Google Ads customer clients"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
googleAdsCustomerClientsHandler,
|
|
75
|
+
googleAdsCustomerClientsSchema
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=customer-clients.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/customer-clients.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n getFieldValue,\n resolveGoogleAdsCustomerId,\n resolveGoogleAdsDeveloperToken,\n resolveGoogleAdsLoginCustomerId,\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 googleAdsCustomerClientsSchema = z.object({\n customerId: z\n .string()\n .describe(\"Google Ads customer ID to inspect for visible child accounts. Usually the MCC ID.\"),\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 googleAdsCustomerClientsHandler(\n params: z.infer<typeof googleAdsCustomerClientsSchema>\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_client.client_customer,\",\n \"customer_client.descriptive_name,\",\n \"customer_client.currency_code,\",\n \"customer_client.time_zone,\",\n \"customer_client.manager,\",\n \"customer_client.test_account,\",\n \"customer_client.status\",\n \"FROM customer_client\",\n \"ORDER BY customer_client.descriptive_name\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const customers = result.rows.map((row) => ({\n customer_id: String(getFieldValue(row, \"customerClient.clientCustomer\") ?? \"\").replace(\n \"customers/\",\n \"\"\n ),\n descriptive_name: getFieldValue(row, \"customerClient.descriptiveName\"),\n currency_code: getFieldValue(row, \"customerClient.currencyCode\"),\n time_zone: getFieldValue(row, \"customerClient.timeZone\"),\n is_manager: getFieldValue<boolean>(row, \"customerClient.manager\"),\n is_test_account: getFieldValue<boolean>(row, \"customerClient.testAccount\"),\n status: getFieldValue(row, \"customerClient.status\"),\n }));\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n overview: {\n total_visible_customers: customers.length,\n manager_customers: customers.filter((customer) => customer.is_manager).length,\n client_customers: customers.filter((customer) => customer.is_manager === false).length,\n },\n customers,\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 list visible Google Ads customer clients\"\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAEpB,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,YAAY,EACT,OAAO,EACP,SAAS,mFAAmF;AAAA,EAC/F,iBAAiB,EACd,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAED,eAAsB,gCACpB,QACA;AACA,MAAI;AACF,UAAM,aAAa,2BAA2B,OAAO,UAAU;AAC/D,UAAM,kBAAkB,gCAAgC,OAAO,eAAe;AAC9E,UAAM,iBAAiB,+BAA+B;AAEtD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,GAAG;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAEA,UAAM,YAAY,OAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MAC1C,aAAa,OAAO,cAAc,KAAK,+BAA+B,KAAK,EAAE,EAAE;AAAA,QAC7E;AAAA,QACA;AAAA,MACF;AAAA,MACA,kBAAkB,cAAc,KAAK,gCAAgC;AAAA,MACrE,eAAe,cAAc,KAAK,6BAA6B;AAAA,MAC/D,WAAW,cAAc,KAAK,yBAAyB;AAAA,MACvD,YAAY,cAAuB,KAAK,wBAAwB;AAAA,MAChE,iBAAiB,cAAuB,KAAK,4BAA4B;AAAA,MACzE,QAAQ,cAAc,KAAK,uBAAuB;AAAA,IACpD,EAAE;AAEF,WAAO;AAAA,MACL,WAAW;AAAA,QACT,aAAa;AAAA,QACb,mBAAmB;AAAA,QACnB,UAAU;AAAA,UACR,yBAAyB,UAAU;AAAA,UACnC,mBAAmB,UAAU,OAAO,CAAC,aAAa,SAAS,UAAU,EAAE;AAAA,UACvE,kBAAkB,UAAU,OAAO,CAAC,aAAa,SAAS,eAAe,KAAK,EAAE;AAAA,QAClF;AAAA,QACA;AAAA,QACA,UAAU;AAAA,UACR,WAAW,OAAO,KAAK;AAAA,UACvB,YAAY,OAAO;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { error, object } from "mcp-use/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
getFieldValue,
|
|
5
|
+
resolveGoogleAdsCustomerId,
|
|
6
|
+
resolveGoogleAdsDeveloperToken,
|
|
7
|
+
resolveGoogleAdsLoginCustomerId
|
|
8
|
+
} from "../../utils/google-ads.js";
|
|
9
|
+
import { searchGoogleAds } from "../../services/google-ads/google-ads-client.js";
|
|
10
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
11
|
+
const googleAdsCustomerInfoSchema = z.object({
|
|
12
|
+
customerId: z.string().describe("Google Ads customer ID to inspect. Accepts digits with or without hyphens."),
|
|
13
|
+
loginCustomerId: z.string().optional().describe(
|
|
14
|
+
"Optional manager account ID used as login customer. If omitted, uses GOOGLE_ADS_LOGIN_CUSTOMER_ID when configured."
|
|
15
|
+
)
|
|
16
|
+
});
|
|
17
|
+
async function googleAdsCustomerInfoHandler(params) {
|
|
18
|
+
try {
|
|
19
|
+
const customerId = resolveGoogleAdsCustomerId(params.customerId);
|
|
20
|
+
const loginCustomerId = resolveGoogleAdsLoginCustomerId(params.loginCustomerId);
|
|
21
|
+
const developerToken = resolveGoogleAdsDeveloperToken();
|
|
22
|
+
const result = await searchGoogleAds(
|
|
23
|
+
customerId,
|
|
24
|
+
[
|
|
25
|
+
"SELECT",
|
|
26
|
+
"customer.id,",
|
|
27
|
+
"customer.descriptive_name,",
|
|
28
|
+
"customer.currency_code,",
|
|
29
|
+
"customer.time_zone,",
|
|
30
|
+
"customer.manager,",
|
|
31
|
+
"customer.test_account",
|
|
32
|
+
"FROM customer"
|
|
33
|
+
].join(" "),
|
|
34
|
+
developerToken,
|
|
35
|
+
loginCustomerId
|
|
36
|
+
);
|
|
37
|
+
const row = result.rows[0] ?? {};
|
|
38
|
+
return object(
|
|
39
|
+
stripNulls({
|
|
40
|
+
customer_id: customerId,
|
|
41
|
+
login_customer_id: loginCustomerId,
|
|
42
|
+
customer: {
|
|
43
|
+
id: getFieldValue(row, "customer.id"),
|
|
44
|
+
descriptive_name: getFieldValue(row, "customer.descriptiveName"),
|
|
45
|
+
currency_code: getFieldValue(row, "customer.currencyCode"),
|
|
46
|
+
time_zone: getFieldValue(row, "customer.timeZone"),
|
|
47
|
+
is_manager: getFieldValue(row, "customer.manager"),
|
|
48
|
+
is_test_account: getFieldValue(row, "customer.testAccount")
|
|
49
|
+
},
|
|
50
|
+
metadata: {
|
|
51
|
+
row_count: result.rows.length,
|
|
52
|
+
request_id: result.requestId
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return error(err instanceof Error ? err.message : "Failed to fetch Google Ads customer info");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
googleAdsCustomerInfoHandler,
|
|
62
|
+
googleAdsCustomerInfoSchema
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=customer-info.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/customer-info.ts"],
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n getFieldValue,\n resolveGoogleAdsCustomerId,\n resolveGoogleAdsDeveloperToken,\n resolveGoogleAdsLoginCustomerId,\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 googleAdsCustomerInfoSchema = z.object({\n customerId: z.string().describe(\"Google Ads customer ID to inspect. 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 googleAdsCustomerInfoHandler(\n params: z.infer<typeof googleAdsCustomerInfoSchema>\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.id,\",\n \"customer.descriptive_name,\",\n \"customer.currency_code,\",\n \"customer.time_zone,\",\n \"customer.manager,\",\n \"customer.test_account\",\n \"FROM customer\",\n ].join(\" \"),\n developerToken,\n loginCustomerId\n );\n\n const row = result.rows[0] ?? {};\n\n return object(\n stripNulls({\n customer_id: customerId,\n login_customer_id: loginCustomerId,\n customer: {\n id: getFieldValue(row, \"customer.id\"),\n descriptive_name: getFieldValue(row, \"customer.descriptiveName\"),\n currency_code: getFieldValue(row, \"customer.currencyCode\"),\n time_zone: getFieldValue(row, \"customer.timeZone\"),\n is_manager: getFieldValue(row, \"customer.manager\"),\n is_test_account: getFieldValue(row, \"customer.testAccount\"),\n },\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 customer info\");\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAEpB,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,YAAY,EAAE,OAAO,EAAE,SAAS,4EAA4E;AAAA,EAC5G,iBAAiB,EACd,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAED,eAAsB,6BACpB,QACA;AACA,MAAI;AACF,UAAM,aAAa,2BAA2B,OAAO,UAAU;AAC/D,UAAM,kBAAkB,gCAAgC,OAAO,eAAe;AAC9E,UAAM,iBAAiB,+BAA+B;AAEtD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,GAAG;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAEA,UAAM,MAAM,OAAO,KAAK,CAAC,KAAK,CAAC;AAE/B,WAAO;AAAA,MACL,WAAW;AAAA,QACT,aAAa;AAAA,QACb,mBAAmB;AAAA,QACnB,UAAU;AAAA,UACR,IAAI,cAAc,KAAK,aAAa;AAAA,UACpC,kBAAkB,cAAc,KAAK,0BAA0B;AAAA,UAC/D,eAAe,cAAc,KAAK,uBAAuB;AAAA,UACzD,WAAW,cAAc,KAAK,mBAAmB;AAAA,UACjD,YAAY,cAAc,KAAK,kBAAkB;AAAA,UACjD,iBAAiB,cAAc,KAAK,sBAAsB;AAAA,QAC5D;AAAA,QACA,UAAU;AAAA,UACR,WAAW,OAAO,KAAK;AAAA,UACvB,YAAY,OAAO;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,MAAM,eAAe,QAAQ,IAAI,UAAU,0CAA0C;AAAA,EAC9F;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./customer-info.js";
|
|
2
|
+
export * from "./customer-clients.js";
|
|
3
|
+
export * from "./account-overview.js";
|
|
4
|
+
export * from "./campaign-performance.js";
|
|
5
|
+
export * from "./time-series.js";
|
|
6
|
+
export * from "./search-terms-summary.js";
|
|
7
|
+
export * from "./channel-mix.js";
|
|
8
|
+
export * from "./account-risks.js";
|
|
9
|
+
export * from "./break-even-analysis.js";
|
|
10
|
+
export * from "./scaling-health.js";
|
|
11
|
+
export * from "./compare-accounts.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/google-ads/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./customer-info.js\";\nexport * from \"./customer-clients.js\";\nexport * from \"./account-overview.js\";\nexport * from \"./campaign-performance.js\";\nexport * from \"./time-series.js\";\nexport * from \"./search-terms-summary.js\";\nexport * from \"./channel-mix.js\";\nexport * from \"./account-risks.js\";\nexport * from \"./break-even-analysis.js\";\nexport * from \"./scaling-health.js\";\nexport * from \"./compare-accounts.js\";\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|