mcp-google-ads 1.1.0 → 1.2.2
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 +55 -4
- package/dist/adGroupBuilder.d.ts +26 -0
- package/dist/adGroupBuilder.js +17 -0
- package/dist/assetHelpers.d.ts +55 -0
- package/dist/assetHelpers.js +129 -0
- package/dist/auth-cli.js +13 -2
- package/dist/build-info.json +2 -2
- package/dist/campaignBuilder.d.ts +39 -0
- package/dist/campaignBuilder.js +65 -0
- package/dist/imageAsset.d.ts +51 -0
- package/dist/imageAsset.js +153 -0
- package/dist/index.js +714 -58
- package/dist/removeHelpers.d.ts +41 -0
- package/dist/removeHelpers.js +68 -0
- package/dist/tools.js +268 -8
- package/dist/validateDemandGenAd.d.ts +62 -0
- package/dist/validateDemandGenAd.js +96 -0
- package/dist/validateRsa.d.ts +11 -0
- package/dist/validateRsa.js +23 -18
- package/package.json +3 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface RemoveArgs {
|
|
2
|
+
customer_id?: string;
|
|
3
|
+
campaign_ids?: string[];
|
|
4
|
+
ad_group_ids?: string[];
|
|
5
|
+
ad_ids?: string[];
|
|
6
|
+
confirm?: boolean;
|
|
7
|
+
labels?: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function coerceStringArray(v: unknown): string[] | undefined;
|
|
10
|
+
export declare function normalizeRemoveArgs(raw: Record<string, unknown> | undefined): RemoveArgs;
|
|
11
|
+
export type ValidationResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function validateRemoveInput(args: RemoveArgs): ValidationResult;
|
|
18
|
+
export interface RemovePreview {
|
|
19
|
+
dry_run: true;
|
|
20
|
+
message: string;
|
|
21
|
+
customer_id: string;
|
|
22
|
+
would_remove: {
|
|
23
|
+
campaigns: string[];
|
|
24
|
+
ad_groups: string[];
|
|
25
|
+
ads: string[];
|
|
26
|
+
};
|
|
27
|
+
labels_to_apply: string[];
|
|
28
|
+
removal_order: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function buildRemovePreview(args: RemoveArgs): RemovePreview;
|
|
31
|
+
export type RemovalStep = {
|
|
32
|
+
type: "ads";
|
|
33
|
+
ids: string[];
|
|
34
|
+
} | {
|
|
35
|
+
type: "ad_groups";
|
|
36
|
+
ids: string[];
|
|
37
|
+
} | {
|
|
38
|
+
type: "campaigns";
|
|
39
|
+
ids: string[];
|
|
40
|
+
};
|
|
41
|
+
export declare function orderRemovalsChildUp(args: RemoveArgs): RemovalStep[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function coerceStringArray(v) {
|
|
2
|
+
if (v == null) return void 0;
|
|
3
|
+
if (Array.isArray(v)) return v.map(String);
|
|
4
|
+
if (typeof v === "string") {
|
|
5
|
+
const trimmed = v.trim();
|
|
6
|
+
if (trimmed.startsWith("[")) {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(trimmed);
|
|
9
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return [trimmed];
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
function normalizeRemoveArgs(raw) {
|
|
18
|
+
const r = raw ?? {};
|
|
19
|
+
return {
|
|
20
|
+
customer_id: typeof r.customer_id === "string" ? r.customer_id : void 0,
|
|
21
|
+
campaign_ids: coerceStringArray(r.campaign_ids),
|
|
22
|
+
ad_group_ids: coerceStringArray(r.ad_group_ids),
|
|
23
|
+
ad_ids: coerceStringArray(r.ad_ids),
|
|
24
|
+
confirm: r.confirm === true || r.confirm === "true",
|
|
25
|
+
labels: coerceStringArray(r.labels)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function validateRemoveInput(args) {
|
|
29
|
+
const hasCampaigns = (args.campaign_ids?.length ?? 0) > 0;
|
|
30
|
+
const hasAdGroups = (args.ad_group_ids?.length ?? 0) > 0;
|
|
31
|
+
const hasAds = (args.ad_ids?.length ?? 0) > 0;
|
|
32
|
+
if (!hasCampaigns && !hasAdGroups && !hasAds) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: "No item IDs provided. Specify campaign_ids, ad_group_ids, or ad_ids."
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return { ok: true };
|
|
39
|
+
}
|
|
40
|
+
function buildRemovePreview(args) {
|
|
41
|
+
return {
|
|
42
|
+
dry_run: true,
|
|
43
|
+
message: "DRY RUN. Nothing removed. Pass confirm: true to actually remove.",
|
|
44
|
+
customer_id: args.customer_id ?? "",
|
|
45
|
+
would_remove: {
|
|
46
|
+
campaigns: args.campaign_ids ?? [],
|
|
47
|
+
ad_groups: args.ad_group_ids ?? [],
|
|
48
|
+
ads: args.ad_ids ?? []
|
|
49
|
+
},
|
|
50
|
+
labels_to_apply: args.labels ?? [],
|
|
51
|
+
removal_order: "ads \u2192 ad_groups \u2192 campaigns (child-up, so parent removes don't fail on enabled children)"
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function orderRemovalsChildUp(args) {
|
|
55
|
+
const steps = [];
|
|
56
|
+
if (args.ad_ids?.length) steps.push({ type: "ads", ids: args.ad_ids });
|
|
57
|
+
if (args.ad_group_ids?.length) steps.push({ type: "ad_groups", ids: args.ad_group_ids });
|
|
58
|
+
if (args.campaign_ids?.length) steps.push({ type: "campaigns", ids: args.campaign_ids });
|
|
59
|
+
return steps;
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
buildRemovePreview,
|
|
63
|
+
coerceStringArray,
|
|
64
|
+
normalizeRemoveArgs,
|
|
65
|
+
orderRemovalsChildUp,
|
|
66
|
+
validateRemoveInput
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=removeHelpers.js.map
|
package/dist/tools.js
CHANGED
|
@@ -104,21 +104,56 @@ const tools = [
|
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
name: "google_ads_create_campaign",
|
|
107
|
-
description: "Create a new campaign (will be PAUSED until approved).
|
|
107
|
+
description: "Create a new campaign (will be PAUSED until approved). Defaults: channel_type=SEARCH, bidding_strategy=MANUAL_CPC (SEARCH) or MAXIMIZE_CONVERSIONS (DEMAND_GEN), language_id=1000 (English). For DEMAND_GEN provide geo_target_ids (Google Ads geo target constant IDs, e.g. '21134' for Alaska). TARGET_CPA requires target_cpa (dollars). MAXIMIZE_CLICKS may take target_cpc_cap (dollars). start_date/end_date are YYYY-MM-DD.",
|
|
108
108
|
inputSchema: {
|
|
109
109
|
additionalProperties: false,
|
|
110
110
|
type: "object",
|
|
111
111
|
properties: {
|
|
112
112
|
customer_id: { type: "string" },
|
|
113
113
|
name: { type: "string" },
|
|
114
|
-
daily_budget: { type: "number", description: "Daily budget in dollars" }
|
|
114
|
+
daily_budget: { type: "number", description: "Daily budget in dollars" },
|
|
115
|
+
channel_type: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["SEARCH", "DEMAND_GEN"],
|
|
118
|
+
description: "Advertising channel type. Defaults to SEARCH."
|
|
119
|
+
},
|
|
120
|
+
bidding_strategy: {
|
|
121
|
+
type: "string",
|
|
122
|
+
enum: ["MANUAL_CPC", "MAXIMIZE_CLICKS", "MAXIMIZE_CONVERSIONS", "TARGET_CPA"],
|
|
123
|
+
description: "Bidding strategy. Defaults to MANUAL_CPC for SEARCH, MAXIMIZE_CONVERSIONS for DEMAND_GEN."
|
|
124
|
+
},
|
|
125
|
+
target_cpa: {
|
|
126
|
+
type: "number",
|
|
127
|
+
description: "Target CPA in dollars (required if bidding_strategy=TARGET_CPA)."
|
|
128
|
+
},
|
|
129
|
+
target_cpc_cap: {
|
|
130
|
+
type: "number",
|
|
131
|
+
description: "Optional CPC ceiling in dollars for MAXIMIZE_CLICKS strategy."
|
|
132
|
+
},
|
|
133
|
+
geo_target_ids: {
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "string" },
|
|
136
|
+
description: "Google Ads geo target constant IDs (e.g. '21134' = Alaska, '21141' = Maine)."
|
|
137
|
+
},
|
|
138
|
+
language_id: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Language constant ID. Defaults to '1000' (English)."
|
|
141
|
+
},
|
|
142
|
+
start_date: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "YYYY-MM-DD start date (optional)."
|
|
145
|
+
},
|
|
146
|
+
end_date: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "YYYY-MM-DD end date (optional)."
|
|
149
|
+
}
|
|
115
150
|
},
|
|
116
151
|
required: ["name", "daily_budget"]
|
|
117
152
|
}
|
|
118
153
|
},
|
|
119
154
|
{
|
|
120
155
|
name: "google_ads_create_ad_group",
|
|
121
|
-
description: "Create a new ad group (will be PAUSED until approved). Returns ad group ID.",
|
|
156
|
+
description: "Create a new ad group (will be PAUSED until approved). Returns ad group ID. type defaults to SEARCH_STANDARD for back-compat; use DEMAND_GEN_MULTI_ASSET_AD_GROUP for Demand Gen campaigns.",
|
|
122
157
|
inputSchema: {
|
|
123
158
|
additionalProperties: false,
|
|
124
159
|
type: "object",
|
|
@@ -126,7 +161,12 @@ const tools = [
|
|
|
126
161
|
customer_id: { type: "string" },
|
|
127
162
|
campaign_id: { type: "string" },
|
|
128
163
|
name: { type: "string" },
|
|
129
|
-
cpc_bid: { type: "number", description: "CPC bid in dollars" }
|
|
164
|
+
cpc_bid: { type: "number", description: "CPC bid in dollars" },
|
|
165
|
+
type: {
|
|
166
|
+
type: "string",
|
|
167
|
+
enum: ["SEARCH_STANDARD", "DEMAND_GEN_MULTI_ASSET_AD_GROUP"],
|
|
168
|
+
description: "Ad group type. Defaults to SEARCH_STANDARD. Use DEMAND_GEN_MULTI_ASSET_AD_GROUP for Demand Gen campaigns."
|
|
169
|
+
}
|
|
130
170
|
},
|
|
131
171
|
required: ["campaign_id", "name"]
|
|
132
172
|
}
|
|
@@ -220,7 +260,7 @@ const tools = [
|
|
|
220
260
|
},
|
|
221
261
|
{
|
|
222
262
|
name: "google_ads_enable_items",
|
|
223
|
-
description: "Enable paused campaigns, ad groups, or ads. REQUIRES USER APPROVAL. Use after reviewing in Google Ads UI.",
|
|
263
|
+
description: "Enable paused campaigns, ad groups, or ads. REQUIRES USER APPROVAL. Use after reviewing in Google Ads UI. Auto-applies today's `Claude-MM-DD-YY` label; pass `labels` to attach additional custom labels so different enable operations stay distinguishable.",
|
|
224
264
|
inputSchema: {
|
|
225
265
|
additionalProperties: false,
|
|
226
266
|
type: "object",
|
|
@@ -228,13 +268,18 @@ const tools = [
|
|
|
228
268
|
customer_id: { type: "string" },
|
|
229
269
|
campaign_ids: { type: "array", items: { type: "string" } },
|
|
230
270
|
ad_group_ids: { type: "array", items: { type: "string" } },
|
|
231
|
-
ad_ids: { type: "array", items: { type: "string" } }
|
|
271
|
+
ad_ids: { type: "array", items: { type: "string" } },
|
|
272
|
+
labels: {
|
|
273
|
+
type: "array",
|
|
274
|
+
items: { type: "string" },
|
|
275
|
+
description: "Optional custom labels to apply to every enabled item (in addition to the auto-applied `Claude-MM-DD-YY` label). Labels are created if they don't exist."
|
|
276
|
+
}
|
|
232
277
|
}
|
|
233
278
|
}
|
|
234
279
|
},
|
|
235
280
|
{
|
|
236
281
|
name: "google_ads_pause_items",
|
|
237
|
-
description: "Pause enabled campaigns, ad groups, or ads. REQUIRES USER APPROVAL. This will stop items from serving.",
|
|
282
|
+
description: "Pause enabled campaigns, ad groups, or ads. REQUIRES USER APPROVAL. This will stop items from serving. Auto-applies today's `Claude-MM-DD-YY` label; pass `labels` to attach additional custom labels so different pause operations stay distinguishable (e.g. `fix-landing-page-redirect`).",
|
|
238
283
|
inputSchema: {
|
|
239
284
|
additionalProperties: false,
|
|
240
285
|
type: "object",
|
|
@@ -242,10 +287,57 @@ const tools = [
|
|
|
242
287
|
customer_id: { type: "string" },
|
|
243
288
|
campaign_ids: { type: "array", items: { type: "string" } },
|
|
244
289
|
ad_group_ids: { type: "array", items: { type: "string" } },
|
|
245
|
-
ad_ids: { type: "array", items: { type: "string" } }
|
|
290
|
+
ad_ids: { type: "array", items: { type: "string" } },
|
|
291
|
+
labels: {
|
|
292
|
+
type: "array",
|
|
293
|
+
items: { type: "string" },
|
|
294
|
+
description: "Optional custom labels to apply to every paused item (in addition to the auto-applied `Claude-MM-DD-YY` label). Labels are created if they don't exist."
|
|
295
|
+
}
|
|
246
296
|
}
|
|
247
297
|
}
|
|
248
298
|
},
|
|
299
|
+
{
|
|
300
|
+
name: "google_ads_remove_items",
|
|
301
|
+
description: "Remove campaigns, ad groups, or ads permanently. IRREVERSIBLE at the API level (reports on removed resources still work). DRY-RUN BY DEFAULT: omit `confirm` or pass `confirm: false` to get a preview; pass `confirm: true` to actually remove. Labels are applied BEFORE removal so the audit trail survives. Removals run in child-up order (ads \u2192 ad_groups \u2192 campaigns) so parent removes don't fail on enabled children. Auto-applies today's `Claude-MM-DD-YY` label.",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
customer_id: { type: "string" },
|
|
307
|
+
campaign_ids: { type: "array", items: { type: "string" } },
|
|
308
|
+
ad_group_ids: { type: "array", items: { type: "string" } },
|
|
309
|
+
ad_ids: { type: "array", items: { type: "string" } },
|
|
310
|
+
confirm: {
|
|
311
|
+
type: "boolean",
|
|
312
|
+
description: "Must be true to actually remove. Omit or false for dry-run preview."
|
|
313
|
+
},
|
|
314
|
+
labels: {
|
|
315
|
+
type: "array",
|
|
316
|
+
items: { type: "string" },
|
|
317
|
+
description: "Optional custom labels to apply BEFORE removal (label-first ordering keeps the audit trail intact). Labels are created if they don't exist."
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "google_ads_apply_label",
|
|
324
|
+
description: "Attach a label to existing campaigns, ad groups, or ads without changing their status. Label is created if it doesn't exist. Useful for tagging items for audit trails or bulk ops.",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
additionalProperties: false,
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
customer_id: { type: "string" },
|
|
330
|
+
label: {
|
|
331
|
+
type: "string",
|
|
332
|
+
description: "Label name. Created if it doesn't exist."
|
|
333
|
+
},
|
|
334
|
+
campaign_ids: { type: "array", items: { type: "string" } },
|
|
335
|
+
ad_group_ids: { type: "array", items: { type: "string" } },
|
|
336
|
+
ad_ids: { type: "array", items: { type: "string" } }
|
|
337
|
+
},
|
|
338
|
+
required: ["label"]
|
|
339
|
+
}
|
|
340
|
+
},
|
|
249
341
|
{
|
|
250
342
|
name: "google_ads_create_shared_set",
|
|
251
343
|
description: "Create a new shared negative keyword list at account level. Returns the new shared set ID.",
|
|
@@ -590,6 +682,26 @@ const tools = [
|
|
|
590
682
|
required: ["campaign_id", "daily_budget"]
|
|
591
683
|
}
|
|
592
684
|
},
|
|
685
|
+
{
|
|
686
|
+
name: "google_ads_update_campaign_bidding",
|
|
687
|
+
description: "Update a campaign's bidding strategy and/or target CPA / target ROAS. If `strategy` is omitted the current strategy is preserved and only the target values are updated (useful for adding a tCPA to an existing Max Conversions campaign). Dollar amounts are in dollars (converted to micros internally). target_roas is a decimal (e.g., 3.0 = 300%).",
|
|
688
|
+
inputSchema: {
|
|
689
|
+
additionalProperties: false,
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
customer_id: { type: "string" },
|
|
693
|
+
campaign_id: { type: "string", description: "The numeric string campaign ID to update" },
|
|
694
|
+
strategy: {
|
|
695
|
+
type: "string",
|
|
696
|
+
enum: ["MAXIMIZE_CONVERSIONS", "MAXIMIZE_CONVERSION_VALUE", "TARGET_CPA", "TARGET_ROAS", "MANUAL_CPC", "MAXIMIZE_CLICKS"],
|
|
697
|
+
description: "Optional. New bidding strategy. If omitted, keeps current strategy and only updates target values."
|
|
698
|
+
},
|
|
699
|
+
target_cpa_dollars: { type: "number", description: "Target CPA in dollars. Applies to MAXIMIZE_CONVERSIONS (optional ceiling) or TARGET_CPA (required)." },
|
|
700
|
+
target_roas: { type: "number", description: "Target ROAS as decimal (3.0 = 300%). Applies to MAXIMIZE_CONVERSION_VALUE or TARGET_ROAS." }
|
|
701
|
+
},
|
|
702
|
+
required: ["campaign_id"]
|
|
703
|
+
}
|
|
704
|
+
},
|
|
593
705
|
{
|
|
594
706
|
name: "google_ads_gaql_query",
|
|
595
707
|
description: "Execute a raw GAQL (Google Ads Query Language) query. Use this for custom reports or accessing any Google Ads API resource not covered by other tools. See https://developers.google.com/google-ads/api/docs/query/overview for GAQL syntax.",
|
|
@@ -603,6 +715,60 @@ const tools = [
|
|
|
603
715
|
required: ["query"]
|
|
604
716
|
}
|
|
605
717
|
},
|
|
718
|
+
{
|
|
719
|
+
name: "google_ads_update_asset_urls",
|
|
720
|
+
description: "Update the final_urls on one or more assets (e.g. sitelinks, callouts). DRY-RUN BY DEFAULT: omit `confirm` or pass `confirm: false` to get a preview. Updating an asset's URL affects EVERY campaign/ad group/customer link that uses that asset ID -- use GAQL against customer_asset/campaign_asset/ad_group_asset to check attachments first.",
|
|
721
|
+
inputSchema: {
|
|
722
|
+
additionalProperties: false,
|
|
723
|
+
type: "object",
|
|
724
|
+
properties: {
|
|
725
|
+
customer_id: { type: "string" },
|
|
726
|
+
updates: {
|
|
727
|
+
type: "array",
|
|
728
|
+
description: "List of assets to update.",
|
|
729
|
+
items: {
|
|
730
|
+
type: "object",
|
|
731
|
+
additionalProperties: false,
|
|
732
|
+
properties: {
|
|
733
|
+
asset_id: { type: "string", description: "Numeric asset ID" },
|
|
734
|
+
final_urls: {
|
|
735
|
+
type: "array",
|
|
736
|
+
items: { type: "string" },
|
|
737
|
+
description: "New final URLs. Must start with http:// or https://."
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
required: ["asset_id", "final_urls"]
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
confirm: {
|
|
744
|
+
type: "boolean",
|
|
745
|
+
description: "Must be true to actually update. Omit or false for dry-run preview."
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
required: ["updates"]
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "google_ads_pause_asset_links",
|
|
753
|
+
description: "Pause asset links (customer_asset, campaign_asset, or ad_group_asset). Use this to stop a sitelink from serving without deleting the underlying asset. DRY-RUN BY DEFAULT: omit `confirm` or pass `confirm: false` to get a preview. Resource name form: customers/{cid}/customerAssets/{assetId}~SITELINK, customers/{cid}/campaignAssets/{campId}~{assetId}~SITELINK, or customers/{cid}/adGroupAssets/{agId}~{assetId}~SITELINK.",
|
|
754
|
+
inputSchema: {
|
|
755
|
+
additionalProperties: false,
|
|
756
|
+
type: "object",
|
|
757
|
+
properties: {
|
|
758
|
+
customer_id: { type: "string" },
|
|
759
|
+
resource_names: {
|
|
760
|
+
type: "array",
|
|
761
|
+
items: { type: "string" },
|
|
762
|
+
description: "Full resource names of the asset links to pause."
|
|
763
|
+
},
|
|
764
|
+
confirm: {
|
|
765
|
+
type: "boolean",
|
|
766
|
+
description: "Must be true to actually pause. Omit or false for dry-run preview."
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
required: ["resource_names"]
|
|
770
|
+
}
|
|
771
|
+
},
|
|
606
772
|
{
|
|
607
773
|
name: "google_ads_keyword_volume",
|
|
608
774
|
description: "Get historical search volume estimates for a list of keywords using the Google Ads Keyword Planner. Returns avg monthly searches, competition level, and CPC bid range for each keyword.",
|
|
@@ -628,6 +794,100 @@ const tools = [
|
|
|
628
794
|
},
|
|
629
795
|
required: ["keywords"]
|
|
630
796
|
}
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: "google_ads_create_demand_gen_multi_asset_ad",
|
|
800
|
+
description: "Create a Demand Gen Multi-Asset ad under a DEMAND_GEN_MULTI_ASSET_AD_GROUP (will be PAUSED until approved). Validates character limits and count caps before the API call: headlines (max 5, \u226440 chars each), long_headlines (max 5, \u226490 chars), descriptions (max 5, \u226490 chars). marketing_image_asset_ids is required (1:1 square images, \u22651); square/portrait/logo assets are optional. call_to_action is a string enum value such as 'LEARN_MORE' or 'SHOP_NOW'. Auto-labels the created ad.",
|
|
801
|
+
inputSchema: {
|
|
802
|
+
additionalProperties: false,
|
|
803
|
+
type: "object",
|
|
804
|
+
properties: {
|
|
805
|
+
customer_id: { type: "string" },
|
|
806
|
+
ad_group_id: { type: "string" },
|
|
807
|
+
final_urls: { type: "array", items: { type: "string" } },
|
|
808
|
+
business_name: { type: "string" },
|
|
809
|
+
call_to_action: {
|
|
810
|
+
type: "string",
|
|
811
|
+
description: "CallToAction enum value, e.g. LEARN_MORE, SHOP_NOW, SIGN_UP, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_QUOTE, APPLY_NOW, SUBSCRIBE, BUY_NOW, DONATE_NOW, ORDER_NOW, PLAY_NOW, SEE_MORE, START_NOW, VISIT_SITE, WATCH_NOW."
|
|
812
|
+
},
|
|
813
|
+
marketing_image_asset_ids: {
|
|
814
|
+
type: "array",
|
|
815
|
+
items: { type: "string" },
|
|
816
|
+
description: "1.91:1 landscape marketing image asset IDs (min 1). Get IDs from google_ads_create_image_asset."
|
|
817
|
+
},
|
|
818
|
+
square_marketing_image_asset_ids: {
|
|
819
|
+
type: "array",
|
|
820
|
+
items: { type: "string" },
|
|
821
|
+
description: "Optional 1:1 square marketing image asset IDs."
|
|
822
|
+
},
|
|
823
|
+
portrait_marketing_image_asset_ids: {
|
|
824
|
+
type: "array",
|
|
825
|
+
items: { type: "string" },
|
|
826
|
+
description: "Optional 4:5 portrait marketing image asset IDs."
|
|
827
|
+
},
|
|
828
|
+
logo_image_asset_ids: {
|
|
829
|
+
type: "array",
|
|
830
|
+
items: { type: "string" },
|
|
831
|
+
description: "Optional 1:1 logo image asset IDs."
|
|
832
|
+
},
|
|
833
|
+
headlines: {
|
|
834
|
+
type: "array",
|
|
835
|
+
description: "Max 5 headlines, each \u226440 characters. Each item is a string or { text, pinned_position? }.",
|
|
836
|
+
items: {
|
|
837
|
+
oneOf: [
|
|
838
|
+
{ type: "string" },
|
|
839
|
+
{
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: {
|
|
842
|
+
text: { type: "string" },
|
|
843
|
+
pinned_position: { type: "number" }
|
|
844
|
+
},
|
|
845
|
+
required: ["text"]
|
|
846
|
+
}
|
|
847
|
+
]
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
long_headlines: {
|
|
851
|
+
type: "array",
|
|
852
|
+
description: "Max 5 long headlines, each \u226490 characters.",
|
|
853
|
+
items: { type: "string" }
|
|
854
|
+
},
|
|
855
|
+
descriptions: {
|
|
856
|
+
type: "array",
|
|
857
|
+
description: "Max 5 descriptions, each \u226490 characters.",
|
|
858
|
+
items: { type: "string" }
|
|
859
|
+
},
|
|
860
|
+
labels: {
|
|
861
|
+
type: "array",
|
|
862
|
+
items: { type: "string" },
|
|
863
|
+
description: "Optional additional labels (auto-applied Claude-MM-DD-YY label is added regardless)."
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
required: [
|
|
867
|
+
"ad_group_id",
|
|
868
|
+
"final_urls",
|
|
869
|
+
"business_name",
|
|
870
|
+
"call_to_action",
|
|
871
|
+
"marketing_image_asset_ids",
|
|
872
|
+
"headlines",
|
|
873
|
+
"descriptions"
|
|
874
|
+
]
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: "google_ads_create_image_asset",
|
|
879
|
+
description: "Upload an image asset for use in Demand Gen (or other image-capable) ads. Provide exactly one of file_path (absolute path to PNG/JPG/GIF on disk) or base64_data (raw base64, no data URL prefix). Validates mime type, max 5MB, min dimensions 600x314 (Demand Gen minimum). Auto-labels the created asset. Returns {asset_id, resource_name, name, bytes, mime_type}.",
|
|
880
|
+
inputSchema: {
|
|
881
|
+
additionalProperties: false,
|
|
882
|
+
type: "object",
|
|
883
|
+
properties: {
|
|
884
|
+
customer_id: { type: "string" },
|
|
885
|
+
name: { type: "string", description: "Human-readable asset name (shown in the Google Ads UI)." },
|
|
886
|
+
file_path: { type: "string", description: "Absolute path to the image on disk. Mutually exclusive with base64_data." },
|
|
887
|
+
base64_data: { type: "string", description: "Raw base64-encoded image data (no 'data:image/...;base64,' prefix). Mutually exclusive with file_path." }
|
|
888
|
+
},
|
|
889
|
+
required: ["name"]
|
|
890
|
+
}
|
|
631
891
|
}
|
|
632
892
|
];
|
|
633
893
|
export {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure validation for Demand Gen multi-asset ads. Kept as a standalone module
|
|
3
|
+
* so we don't need to stand up a live Google Ads client to unit test the
|
|
4
|
+
* rules. Rules come from Google Ads API docs for DemandGenMultiAssetAdInfo:
|
|
5
|
+
* - headlines: max 5, each ≤ 40 chars
|
|
6
|
+
* - long_headlines: max 5, each ≤ 90 chars
|
|
7
|
+
* - descriptions: max 5, each ≤ 90 chars
|
|
8
|
+
* - marketing_image_asset_ids: required, ≥ 1 (1.91:1 landscape images)
|
|
9
|
+
* - business_name: required, non-empty
|
|
10
|
+
* - call_to_action: required, non-empty (string CTA enum value)
|
|
11
|
+
* - final_urls: required, ≥ 1
|
|
12
|
+
*/
|
|
13
|
+
export interface DemandGenAdInput {
|
|
14
|
+
final_urls: string[];
|
|
15
|
+
business_name: string;
|
|
16
|
+
call_to_action: string;
|
|
17
|
+
marketing_image_asset_ids: string[];
|
|
18
|
+
square_marketing_image_asset_ids?: string[];
|
|
19
|
+
portrait_marketing_image_asset_ids?: string[];
|
|
20
|
+
logo_image_asset_ids?: string[];
|
|
21
|
+
headlines: Array<string | {
|
|
22
|
+
text: string;
|
|
23
|
+
pinned_position?: number;
|
|
24
|
+
}>;
|
|
25
|
+
long_headlines?: string[];
|
|
26
|
+
descriptions: string[];
|
|
27
|
+
labels?: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface DemandGenAdValidationResult {
|
|
30
|
+
valid: boolean;
|
|
31
|
+
errors: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare const MAX_HEADLINES = 5;
|
|
34
|
+
export declare const MAX_HEADLINE_LEN = 40;
|
|
35
|
+
export declare const MAX_LONG_HEADLINES = 5;
|
|
36
|
+
export declare const MAX_LONG_HEADLINE_LEN = 90;
|
|
37
|
+
export declare const MAX_DESCRIPTIONS = 5;
|
|
38
|
+
export declare const MAX_DESCRIPTION_LEN = 90;
|
|
39
|
+
/**
|
|
40
|
+
* Build the raw Google Ads DemandGenMultiAssetAdInfo payload. Asset IDs in the
|
|
41
|
+
* input are expanded to full resource names. Optional image fields are only
|
|
42
|
+
* set when the caller provided a non-empty list.
|
|
43
|
+
*
|
|
44
|
+
* NOTE: the v23 typed helper for AdService is missing the long_headlines field
|
|
45
|
+
* on DemandGenMultiAssetAdInfo. We emit long_headlines regardless; the payload
|
|
46
|
+
* is submitted via customer.mutateResources which accepts unknown fields and
|
|
47
|
+
* forwards them to the server.
|
|
48
|
+
*/
|
|
49
|
+
export interface DemandGenAdPayload {
|
|
50
|
+
ad_group: string;
|
|
51
|
+
status: number;
|
|
52
|
+
ad: {
|
|
53
|
+
final_urls: string[];
|
|
54
|
+
demand_gen_multi_asset_ad: Record<string, any>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export declare function buildDemandGenAdPayload(args: {
|
|
58
|
+
customer_id_clean: string;
|
|
59
|
+
ad_group_id: string;
|
|
60
|
+
input: DemandGenAdInput;
|
|
61
|
+
}): DemandGenAdPayload;
|
|
62
|
+
export declare function validateDemandGenAd(ad: DemandGenAdInput): DemandGenAdValidationResult;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const MAX_HEADLINES = 5;
|
|
2
|
+
const MAX_HEADLINE_LEN = 40;
|
|
3
|
+
const MAX_LONG_HEADLINES = 5;
|
|
4
|
+
const MAX_LONG_HEADLINE_LEN = 90;
|
|
5
|
+
const MAX_DESCRIPTIONS = 5;
|
|
6
|
+
const MAX_DESCRIPTION_LEN = 90;
|
|
7
|
+
function headlineText(h) {
|
|
8
|
+
return typeof h === "string" ? h : h.text;
|
|
9
|
+
}
|
|
10
|
+
function buildDemandGenAdPayload(args) {
|
|
11
|
+
const { customer_id_clean, ad_group_id, input } = args;
|
|
12
|
+
const assetRef = (id) => ({ asset: `customers/${customer_id_clean}/assets/${id}` });
|
|
13
|
+
const dgAd = {
|
|
14
|
+
business_name: input.business_name,
|
|
15
|
+
call_to_action_text: input.call_to_action,
|
|
16
|
+
marketing_images: input.marketing_image_asset_ids.map(assetRef),
|
|
17
|
+
headlines: input.headlines.map((h) => ({ text: headlineText(h) })),
|
|
18
|
+
descriptions: input.descriptions.map((t) => ({ text: t }))
|
|
19
|
+
};
|
|
20
|
+
if (input.square_marketing_image_asset_ids?.length) {
|
|
21
|
+
dgAd.square_marketing_images = input.square_marketing_image_asset_ids.map(assetRef);
|
|
22
|
+
}
|
|
23
|
+
if (input.portrait_marketing_image_asset_ids?.length) {
|
|
24
|
+
dgAd.portrait_marketing_images = input.portrait_marketing_image_asset_ids.map(assetRef);
|
|
25
|
+
}
|
|
26
|
+
if (input.logo_image_asset_ids?.length) {
|
|
27
|
+
dgAd.logo_images = input.logo_image_asset_ids.map(assetRef);
|
|
28
|
+
}
|
|
29
|
+
if (input.long_headlines?.length) {
|
|
30
|
+
dgAd.long_headlines = input.long_headlines.map((t) => ({ text: t }));
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
ad_group: `customers/${customer_id_clean}/adGroups/${ad_group_id}`,
|
|
34
|
+
status: 3,
|
|
35
|
+
ad: {
|
|
36
|
+
final_urls: input.final_urls,
|
|
37
|
+
demand_gen_multi_asset_ad: dgAd
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function validateDemandGenAd(ad) {
|
|
42
|
+
const errors = [];
|
|
43
|
+
if (!ad.final_urls || ad.final_urls.length === 0) {
|
|
44
|
+
errors.push("At least one final URL is required");
|
|
45
|
+
}
|
|
46
|
+
if (!ad.business_name || !ad.business_name.trim()) {
|
|
47
|
+
errors.push("business_name is required");
|
|
48
|
+
}
|
|
49
|
+
if (!ad.call_to_action || !ad.call_to_action.trim()) {
|
|
50
|
+
errors.push("call_to_action is required (e.g. 'LEARN_MORE')");
|
|
51
|
+
}
|
|
52
|
+
if (!ad.marketing_image_asset_ids || ad.marketing_image_asset_ids.length === 0) {
|
|
53
|
+
errors.push("At least one marketing_image_asset_id is required");
|
|
54
|
+
}
|
|
55
|
+
if (!ad.headlines || ad.headlines.length === 0) {
|
|
56
|
+
errors.push("At least one headline is required");
|
|
57
|
+
} else if (ad.headlines.length > MAX_HEADLINES) {
|
|
58
|
+
errors.push(`Maximum ${MAX_HEADLINES} headlines, got ${ad.headlines.length}`);
|
|
59
|
+
}
|
|
60
|
+
(ad.headlines ?? []).forEach((h, i) => {
|
|
61
|
+
const text = headlineText(h);
|
|
62
|
+
if (text.length > MAX_HEADLINE_LEN) {
|
|
63
|
+
errors.push(`Headline ${i + 1} too long (${text.length}/${MAX_HEADLINE_LEN}): "${text}"`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (ad.long_headlines && ad.long_headlines.length > MAX_LONG_HEADLINES) {
|
|
67
|
+
errors.push(`Maximum ${MAX_LONG_HEADLINES} long_headlines, got ${ad.long_headlines.length}`);
|
|
68
|
+
}
|
|
69
|
+
(ad.long_headlines ?? []).forEach((t, i) => {
|
|
70
|
+
if (t.length > MAX_LONG_HEADLINE_LEN) {
|
|
71
|
+
errors.push(`Long headline ${i + 1} too long (${t.length}/${MAX_LONG_HEADLINE_LEN}): "${t}"`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (!ad.descriptions || ad.descriptions.length === 0) {
|
|
75
|
+
errors.push("At least one description is required");
|
|
76
|
+
} else if (ad.descriptions.length > MAX_DESCRIPTIONS) {
|
|
77
|
+
errors.push(`Maximum ${MAX_DESCRIPTIONS} descriptions, got ${ad.descriptions.length}`);
|
|
78
|
+
}
|
|
79
|
+
(ad.descriptions ?? []).forEach((t, i) => {
|
|
80
|
+
if (t.length > MAX_DESCRIPTION_LEN) {
|
|
81
|
+
errors.push(`Description ${i + 1} too long (${t.length}/${MAX_DESCRIPTION_LEN}): "${t}"`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return { valid: errors.length === 0, errors };
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
MAX_DESCRIPTIONS,
|
|
88
|
+
MAX_DESCRIPTION_LEN,
|
|
89
|
+
MAX_HEADLINES,
|
|
90
|
+
MAX_HEADLINE_LEN,
|
|
91
|
+
MAX_LONG_HEADLINES,
|
|
92
|
+
MAX_LONG_HEADLINE_LEN,
|
|
93
|
+
buildDemandGenAdPayload,
|
|
94
|
+
validateDemandGenAd
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=validateDemandGenAd.js.map
|
package/dist/validateRsa.d.ts
CHANGED
|
@@ -22,3 +22,14 @@ export interface RsaValidationResult {
|
|
|
22
22
|
errors: string[];
|
|
23
23
|
}
|
|
24
24
|
export declare function validateRsa(ad: RsaInput): RsaValidationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Compute the length Google Ads will actually validate against, after
|
|
27
|
+
* substituting dynamic insertions:
|
|
28
|
+
* - {KeyWord:default} / {Keyword:default} / {keyword:default} / {KEYWORD:default}
|
|
29
|
+
* → rendered as the default text
|
|
30
|
+
* - {CUSTOMIZER.Name:default} → rendered as the default text
|
|
31
|
+
* - {CUSTOMIZER.Name} → rendered as a conservative estimate
|
|
32
|
+
*
|
|
33
|
+
* Applies to headlines, descriptions, and display paths alike.
|
|
34
|
+
*/
|
|
35
|
+
export declare function effectiveTextLength(text: string): number;
|