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.
- package/dist/adGroupBuilder.d.ts +7 -6
- package/dist/adGroupBuilder.js +1 -3
- package/dist/assetHelpers.d.ts +43 -0
- package/dist/assetHelpers.js +93 -0
- package/dist/build-info.json +2 -2
- package/dist/campaignBuilder.js +9 -3
- package/dist/experimentBuilder.d.ts +36 -0
- package/dist/experimentBuilder.js +52 -0
- package/dist/index.js +973 -19
- package/dist/resilience.js +8 -2
- package/dist/resourceNames.d.ts +1 -0
- package/dist/resourceNames.js +7 -0
- package/dist/tools.js +330 -0
- package/dist/writeGate.js +15 -1
- package/package.json +2 -2
package/dist/adGroupBuilder.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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;
|
package/dist/adGroupBuilder.js
CHANGED
|
@@ -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" ?
|
|
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
|
package/dist/assetHelpers.d.ts
CHANGED
|
@@ -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;
|
package/dist/assetHelpers.js
CHANGED
|
@@ -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
|
package/dist/build-info.json
CHANGED
package/dist/campaignBuilder.js
CHANGED
|
@@ -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:
|
|
29
|
+
target_google_search: true,
|
|
25
30
|
target_search_network: false,
|
|
26
|
-
target_content_network:
|
|
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
|