mcp-google-ads 1.4.2 → 1.5.0

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.
@@ -2,13 +2,14 @@
2
2
  * Pure builder for ad-group creation payloads. Separate from the manager so
3
3
  * it can be unit-tested without stubbing the live API.
4
4
  *
5
- * `type` for DEMAND_GEN_MULTI_ASSET_AD_GROUP emits the raw proto value 21
6
- * because the google-ads-api v23 enum map does not include it; the typed
7
- * AdGroup create path validates against the local enum, so DG creation must
8
- * flow through customer.mutateResources (which trusts the numeric value).
5
+ * DEMAND_GEN_MULTI_ASSET_AD_GROUP uses the string enum name rather than the
6
+ * integer 21. The v23 library's local enum map does not include 21, so integer
7
+ * 21 gets JSON-serialized as "UNKNOWN_ENUM_VALUE_AdGroupType_21" which the API
8
+ * rejects. Passing the string name directly bypasses the local enum lookup.
9
+ * DG creation still flows through customer.mutateResources to avoid the typed
10
+ * AdGroup.create path which only accepts known client-side enum values.
9
11
  */
10
12
  export type AdGroupTypeName = "SEARCH_STANDARD" | "DEMAND_GEN_MULTI_ASSET_AD_GROUP";
11
- export declare const AD_GROUP_TYPE_DEMAND_GEN_MULTI_ASSET = 21;
12
13
  export interface AdGroupCreateInput {
13
14
  customer_id_clean: string;
14
15
  name: string;
@@ -21,6 +22,6 @@ export interface AdGroupCreatePayload {
21
22
  campaign: string;
22
23
  status: number;
23
24
  cpc_bid_micros: number;
24
- type: number;
25
+ type: number | string;
25
26
  }
26
27
  export declare function buildAdGroupCreatePayload(input: AdGroupCreateInput): AdGroupCreatePayload;
@@ -1,7 +1,6 @@
1
1
  import { enums } from "google-ads-api";
2
- const AD_GROUP_TYPE_DEMAND_GEN_MULTI_ASSET = 21;
3
2
  function buildAdGroupCreatePayload(input) {
4
- const typeEnum = input.type === "DEMAND_GEN_MULTI_ASSET_AD_GROUP" ? AD_GROUP_TYPE_DEMAND_GEN_MULTI_ASSET : enums.AdGroupType.SEARCH_STANDARD;
3
+ const typeEnum = input.type === "DEMAND_GEN_MULTI_ASSET_AD_GROUP" ? "DEMAND_GEN_MULTI_ASSET_AD_GROUP" : enums.AdGroupType.SEARCH_STANDARD;
5
4
  return {
6
5
  name: input.name,
7
6
  campaign: `customers/${input.customer_id_clean}/campaigns/${input.campaign_id}`,
@@ -11,7 +10,6 @@ function buildAdGroupCreatePayload(input) {
11
10
  };
12
11
  }
13
12
  export {
14
- AD_GROUP_TYPE_DEMAND_GEN_MULTI_ASSET,
15
13
  buildAdGroupCreatePayload
16
14
  };
17
15
  //# sourceMappingURL=adGroupBuilder.js.map
@@ -52,4 +52,47 @@ export interface PauseLinksDryRun {
52
52
  ad_group_asset: string[];
53
53
  };
54
54
  }
55
+ export interface CreateSitelinkArgs {
56
+ customer_id?: string;
57
+ link_text: string;
58
+ final_urls: string[];
59
+ description1?: string;
60
+ description2?: string;
61
+ confirm?: boolean;
62
+ }
63
+ export interface CreateSitelinkDryRun {
64
+ dry_run: true;
65
+ message: string;
66
+ customer_id: string;
67
+ link_text: string;
68
+ final_urls: string[];
69
+ description1?: string;
70
+ description2?: string;
71
+ }
72
+ export declare function normalizeCreateSitelinkArgs(raw: Record<string, unknown> | undefined): CreateSitelinkArgs | {
73
+ error: string;
74
+ };
75
+ export declare function buildCreateSitelinkDryRun(args: CreateSitelinkArgs): CreateSitelinkDryRun;
76
+ export interface ReplaceSitelinkArgs {
77
+ customer_id?: string;
78
+ old_asset_id: string;
79
+ new_final_urls: string[];
80
+ new_link_text?: string;
81
+ new_description1?: string;
82
+ new_description2?: string;
83
+ confirm?: boolean;
84
+ }
85
+ export interface ReplaceSitelinkDryRun {
86
+ dry_run: true;
87
+ message: string;
88
+ customer_id: string;
89
+ old_asset_id: string;
90
+ new_final_urls: string[];
91
+ new_link_text_override?: string;
92
+ warning: string;
93
+ }
94
+ export declare function normalizeReplaceSitelinkArgs(raw: Record<string, unknown> | undefined): ReplaceSitelinkArgs | {
95
+ error: string;
96
+ };
97
+ export declare function buildReplaceSitelinkDryRun(args: ReplaceSitelinkArgs): ReplaceSitelinkDryRun;
55
98
  export declare function buildPauseLinksDryRun(args: PauseAssetLinksArgs): PauseLinksDryRun;
@@ -99,6 +99,95 @@ function buildUpdateUrlsDryRun(args) {
99
99
  warning: "Updating an asset's final_urls affects EVERY campaign/ad group/customer link that uses this asset ID. Verify attachments before confirming."
100
100
  };
101
101
  }
102
+ function validateSitelinkText(value, field, max) {
103
+ if (typeof value !== "string") return { ok: false, error: `${field} must be a string` };
104
+ const t = value.trim();
105
+ if (!t) return { ok: false, error: `${field} must be non-empty` };
106
+ if (t.length > max) return { ok: false, error: `${field} exceeds ${max} chars (got ${t.length})` };
107
+ return { ok: true, value: t };
108
+ }
109
+ function normalizeCreateSitelinkArgs(raw) {
110
+ const r = raw ?? {};
111
+ const customer_id = typeof r.customer_id === "string" ? r.customer_id : void 0;
112
+ const linkText = validateSitelinkText(r.link_text, "link_text", 25);
113
+ if (!linkText.ok) return { error: linkText.error };
114
+ const urls = validateFinalUrls(r.final_urls);
115
+ if (!urls.ok) return { error: urls.error };
116
+ const out = {
117
+ customer_id,
118
+ link_text: linkText.value,
119
+ final_urls: urls.urls,
120
+ confirm: r.confirm === true || r.confirm === "true"
121
+ };
122
+ if (r.description1 !== void 0 && r.description1 !== null && r.description1 !== "") {
123
+ const d = validateSitelinkText(r.description1, "description1", 35);
124
+ if (!d.ok) return { error: d.error };
125
+ out.description1 = d.value;
126
+ }
127
+ if (r.description2 !== void 0 && r.description2 !== null && r.description2 !== "") {
128
+ const d = validateSitelinkText(r.description2, "description2", 35);
129
+ if (!d.ok) return { error: d.error };
130
+ out.description2 = d.value;
131
+ }
132
+ if (out.description1 && !out.description2 || !out.description1 && out.description2) {
133
+ return { error: "description1 and description2 must both be set or both omitted" };
134
+ }
135
+ return out;
136
+ }
137
+ function buildCreateSitelinkDryRun(args) {
138
+ return {
139
+ dry_run: true,
140
+ message: "DRY RUN. Nothing created. Pass confirm: true to actually create the sitelink asset.",
141
+ customer_id: args.customer_id ?? "",
142
+ link_text: args.link_text,
143
+ final_urls: args.final_urls,
144
+ description1: args.description1,
145
+ description2: args.description2
146
+ };
147
+ }
148
+ function normalizeReplaceSitelinkArgs(raw) {
149
+ const r = raw ?? {};
150
+ const customer_id = typeof r.customer_id === "string" ? r.customer_id : void 0;
151
+ const assetIdRaw = typeof r.old_asset_id === "string" ? r.old_asset_id.trim() : typeof r.old_asset_id === "number" ? String(r.old_asset_id) : "";
152
+ if (!assetIdRaw || !/^\d+$/.test(assetIdRaw)) {
153
+ return { error: `invalid old_asset_id: ${JSON.stringify(r.old_asset_id)}. Expected numeric asset ID.` };
154
+ }
155
+ const urls = validateFinalUrls(r.new_final_urls);
156
+ if (!urls.ok) return { error: urls.error };
157
+ const out = {
158
+ customer_id,
159
+ old_asset_id: assetIdRaw,
160
+ new_final_urls: urls.urls,
161
+ confirm: r.confirm === true || r.confirm === "true"
162
+ };
163
+ if (r.new_link_text !== void 0 && r.new_link_text !== null && r.new_link_text !== "") {
164
+ const d = validateSitelinkText(r.new_link_text, "new_link_text", 25);
165
+ if (!d.ok) return { error: d.error };
166
+ out.new_link_text = d.value;
167
+ }
168
+ if (r.new_description1 !== void 0 && r.new_description1 !== null && r.new_description1 !== "") {
169
+ const d = validateSitelinkText(r.new_description1, "new_description1", 35);
170
+ if (!d.ok) return { error: d.error };
171
+ out.new_description1 = d.value;
172
+ }
173
+ if (r.new_description2 !== void 0 && r.new_description2 !== null && r.new_description2 !== "") {
174
+ const d = validateSitelinkText(r.new_description2, "new_description2", 35);
175
+ if (!d.ok) return { error: d.error };
176
+ out.new_description2 = d.value;
177
+ }
178
+ return out;
179
+ }
180
+ function buildReplaceSitelinkDryRun(args) {
181
+ return {
182
+ dry_run: true,
183
+ message: "DRY RUN. Nothing changed. Pass confirm: true to replace the sitelink.",
184
+ customer_id: args.customer_id ?? "",
185
+ old_asset_id: args.old_asset_id,
186
+ new_final_urls: args.new_final_urls,
187
+ new_link_text_override: args.new_link_text,
188
+ warning: "Will create a NEW sitelink asset, re-link every campaign/ad-group/customer link currently pointing at old_asset_id, then remove the old links. The old Asset itself is NOT deleted and can still be re-used manually."
189
+ };
190
+ }
102
191
  function buildPauseLinksDryRun(args) {
103
192
  const would = {
104
193
  customer_asset: [],
@@ -119,9 +208,13 @@ function buildPauseLinksDryRun(args) {
119
208
  };
120
209
  }
121
210
  export {
211
+ buildCreateSitelinkDryRun,
122
212
  buildPauseLinksDryRun,
213
+ buildReplaceSitelinkDryRun,
123
214
  buildUpdateUrlsDryRun,
215
+ normalizeCreateSitelinkArgs,
124
216
  normalizePauseAssetLinksArgs,
217
+ normalizeReplaceSitelinkArgs,
125
218
  normalizeUpdateAssetUrlsArgs,
126
219
  parseAssetLinkResourceName,
127
220
  validateFinalUrls
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha": "baabc65",
3
- "builtAt": "2026-04-18T17:46:42.713Z",
2
+ "sha": "cc6cf6c",
3
+ "builtAt": "2026-04-27T14:57:08.443Z",
4
4
  "embeddedSecrets": true
5
5
  }
@@ -17,15 +17,21 @@ function buildCampaignCreatePayload(input) {
17
17
  const campaign = {
18
18
  name: input.name,
19
19
  status: enums.CampaignStatus.PAUSED,
20
- advertising_channel_type: channelEnum
20
+ advertising_channel_type: channelEnum,
21
+ // EuPoliticalAdvertisingStatus enum: 3 = DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING.
22
+ // Required for all new campaigns in API v23+. Must be a non-zero enum value
23
+ // (proto3 strips default/zero values, so `false`/0 gets omitted and the API
24
+ // rejects with "required field not present").
25
+ contains_eu_political_advertising: enums.EuPoliticalAdvertisingStatus?.DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING ?? 3
21
26
  };
22
27
  if (channelType === "DEMAND_GEN") {
23
28
  campaign.network_settings = {
24
- target_google_search: false,
29
+ target_google_search: true,
25
30
  target_search_network: false,
26
- target_content_network: true,
31
+ target_content_network: false,
27
32
  target_partner_search_network: false
28
33
  };
34
+ campaign.audience_setting = { use_audience_grouped: true };
29
35
  }
30
36
  if (input.start_date) campaign.start_date = input.start_date;
31
37
  if (input.end_date) campaign.end_date = input.end_date;
@@ -0,0 +1,36 @@
1
+ export interface ExperimentCreateInput {
2
+ base_campaign_id: string;
3
+ name: string;
4
+ description?: string;
5
+ /** Suffix appended to the auto-created treatment campaign name. Defaults to " [EXP]" */
6
+ suffix?: string;
7
+ /** Percent of traffic sent to treatment arm (1–99). Defaults to 50. */
8
+ traffic_split_percent?: number;
9
+ start_date?: string;
10
+ end_date?: string;
11
+ }
12
+ export interface ExperimentArmPayload {
13
+ experiment: string;
14
+ name: string;
15
+ control: boolean;
16
+ traffic_split: number;
17
+ campaigns: string[];
18
+ }
19
+ export interface ExperimentPayload {
20
+ name: string;
21
+ description?: string;
22
+ suffix: string;
23
+ type: number;
24
+ status: number;
25
+ start_date?: string;
26
+ end_date?: string;
27
+ }
28
+ export declare function buildExperimentPayload(input: ExperimentCreateInput): ExperimentPayload;
29
+ export declare function buildControlArmPayload(experimentResourceName: string, baseCampaignResourceName: string, trafficSplit: number): ExperimentArmPayload;
30
+ export declare function buildTreatmentArmPayload(experimentResourceName: string, trafficSplit: number): ExperimentArmPayload;
31
+ /** Format YYYY-MM-DD from a Date (or today). */
32
+ export declare function formatDate(d?: Date): string;
33
+ /** Parse an experiment resource name and return the numeric experiment id. */
34
+ export declare function experimentIdFromResourceName(resourceName: string): string;
35
+ /** Parse a campaign resource name and return the numeric campaign id. */
36
+ export declare function campaignIdFromResourceName(resourceName: string): string;
@@ -0,0 +1,52 @@
1
+ import { enums } from "google-ads-api";
2
+ function buildExperimentPayload(input) {
3
+ return {
4
+ name: input.name,
5
+ ...input.description ? { description: input.description } : {},
6
+ suffix: input.suffix ?? " [EXP]",
7
+ type: enums.ExperimentType.SEARCH_CUSTOM,
8
+ // 7
9
+ status: enums.ExperimentStatus.SETUP,
10
+ // 6
11
+ ...input.start_date ? { start_date: input.start_date } : {},
12
+ ...input.end_date ? { end_date: input.end_date } : {}
13
+ };
14
+ }
15
+ function buildControlArmPayload(experimentResourceName, baseCampaignResourceName, trafficSplit) {
16
+ return {
17
+ experiment: experimentResourceName,
18
+ name: "Control",
19
+ control: true,
20
+ traffic_split: trafficSplit,
21
+ campaigns: [baseCampaignResourceName]
22
+ };
23
+ }
24
+ function buildTreatmentArmPayload(experimentResourceName, trafficSplit) {
25
+ return {
26
+ experiment: experimentResourceName,
27
+ name: "Treatment",
28
+ control: false,
29
+ traffic_split: trafficSplit,
30
+ campaigns: []
31
+ // Google auto-creates a copy of the base campaign
32
+ };
33
+ }
34
+ function formatDate(d) {
35
+ const dt = d ?? /* @__PURE__ */ new Date();
36
+ return dt.toISOString().slice(0, 10);
37
+ }
38
+ function experimentIdFromResourceName(resourceName) {
39
+ return resourceName.split("/").pop() ?? "";
40
+ }
41
+ function campaignIdFromResourceName(resourceName) {
42
+ return resourceName.split("/").pop() ?? "";
43
+ }
44
+ export {
45
+ buildControlArmPayload,
46
+ buildExperimentPayload,
47
+ buildTreatmentArmPayload,
48
+ campaignIdFromResourceName,
49
+ experimentIdFromResourceName,
50
+ formatDate
51
+ };
52
+ //# sourceMappingURL=experimentBuilder.js.map