mcp-google-ads 1.4.2 → 1.4.4

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.
@@ -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": "77d847b",
3
+ "builtAt": "2026-04-18T18:38:13.085Z",
4
4
  "embeddedSecrets": true
5
5
  }
package/dist/index.js CHANGED
@@ -28,7 +28,11 @@ import {
28
28
  normalizeUpdateAssetUrlsArgs,
29
29
  normalizePauseAssetLinksArgs,
30
30
  buildUpdateUrlsDryRun,
31
- buildPauseLinksDryRun
31
+ buildPauseLinksDryRun,
32
+ normalizeCreateSitelinkArgs,
33
+ buildCreateSitelinkDryRun,
34
+ normalizeReplaceSitelinkArgs,
35
+ buildReplaceSitelinkDryRun
32
36
  } from "./assetHelpers.js";
33
37
  import {
34
38
  buildCampaignCreatePayload
@@ -1211,7 +1215,7 @@ class GoogleAdsManager {
1211
1215
  else if (/\/adGroupAssets\//.test(rn)) byLevel.ad_group_asset.push(rn);
1212
1216
  else throw new Error(`Unrecognized asset-link resource name: ${rn}`);
1213
1217
  }
1214
- const PAUSED = 2;
1218
+ const PAUSED = enums.AssetLinkStatus.PAUSED;
1215
1219
  const result = { customer_id: cleanId, paused: { customer_asset: 0, campaign_asset: 0, ad_group_asset: 0 } };
1216
1220
  if (byLevel.customer_asset.length > 0) {
1217
1221
  const ops = byLevel.customer_asset.map((rn) => ({ resource_name: rn, status: PAUSED }));
@@ -1240,6 +1244,217 @@ class GoogleAdsManager {
1240
1244
  return result;
1241
1245
  }
1242
1246
  // ============================================
1247
+ // SITELINK CREATE + REPLACE
1248
+ // ============================================
1249
+ async createSitelink(customerId, args) {
1250
+ const customer = this.getCustomer(customerId);
1251
+ const cleanId = customerId.replace(/-/g, "");
1252
+ const sitelink = { link_text: args.link_text };
1253
+ if (args.description1) sitelink.description1 = args.description1;
1254
+ if (args.description2) sitelink.description2 = args.description2;
1255
+ const result = await withResilience(
1256
+ () => customer.assets.create([
1257
+ {
1258
+ type: enums.AssetType.SITELINK,
1259
+ final_urls: args.final_urls,
1260
+ sitelink_asset: sitelink
1261
+ }
1262
+ ]),
1263
+ "createSitelink"
1264
+ );
1265
+ const results = result.results || [];
1266
+ const resourceName = results[0]?.resource_name;
1267
+ const assetId = resourceName ? resourceName.split("/").pop() : void 0;
1268
+ if (resourceName) {
1269
+ await this.autoLabelCreated(customerId, [resourceName], "asset");
1270
+ }
1271
+ return {
1272
+ customer_id: cleanId,
1273
+ asset_id: assetId,
1274
+ resource_name: resourceName,
1275
+ link_text: args.link_text,
1276
+ final_urls: args.final_urls,
1277
+ description1: args.description1,
1278
+ description2: args.description2
1279
+ };
1280
+ }
1281
+ async replaceSitelinkUrl(customerId, args) {
1282
+ const customer = this.getCustomer(customerId);
1283
+ const cleanId = customerId.replace(/-/g, "");
1284
+ const oldAssetRows = await withResilience(
1285
+ () => customer.query(`
1286
+ SELECT
1287
+ asset.id,
1288
+ asset.type,
1289
+ asset.final_urls,
1290
+ asset.sitelink_asset.link_text,
1291
+ asset.sitelink_asset.description1,
1292
+ asset.sitelink_asset.description2
1293
+ FROM asset
1294
+ WHERE asset.id = ${args.old_asset_id}
1295
+ `),
1296
+ "replaceSitelinkUrl.queryAsset"
1297
+ );
1298
+ const oldAsset = oldAssetRows[0]?.asset;
1299
+ if (!oldAsset) {
1300
+ throw new Error(`Asset ${args.old_asset_id} not found in customer ${cleanId}`);
1301
+ }
1302
+ if (oldAsset.type !== enums.AssetType.SITELINK && oldAsset.type !== 13) {
1303
+ throw new Error(
1304
+ `Asset ${args.old_asset_id} is type ${oldAsset.type} (not SITELINK). This tool only replaces sitelink assets; use google_ads_update_asset_urls for other asset types.`
1305
+ );
1306
+ }
1307
+ const link_text = args.new_link_text ?? oldAsset.sitelink_asset?.link_text;
1308
+ if (!link_text) {
1309
+ throw new Error(
1310
+ `Old asset has no link_text and none was provided. Pass new_link_text to override.`
1311
+ );
1312
+ }
1313
+ const description1 = args.new_description1 ?? oldAsset.sitelink_asset?.description1 ?? void 0;
1314
+ const description2 = args.new_description2 ?? oldAsset.sitelink_asset?.description2 ?? void 0;
1315
+ const campaignLinksRows = await withResilience(
1316
+ () => customer.query(`
1317
+ SELECT
1318
+ campaign_asset.resource_name,
1319
+ campaign_asset.campaign,
1320
+ campaign_asset.field_type,
1321
+ campaign_asset.status
1322
+ FROM campaign_asset
1323
+ WHERE campaign_asset.asset = 'customers/${cleanId}/assets/${args.old_asset_id}'
1324
+ AND campaign_asset.field_type = 'SITELINK'
1325
+ AND campaign_asset.status = 'ENABLED'
1326
+ `),
1327
+ "replaceSitelinkUrl.queryCampaignAssets"
1328
+ );
1329
+ const adGroupLinksRows = await withResilience(
1330
+ () => customer.query(`
1331
+ SELECT
1332
+ ad_group_asset.resource_name,
1333
+ ad_group_asset.ad_group,
1334
+ ad_group_asset.field_type,
1335
+ ad_group_asset.status
1336
+ FROM ad_group_asset
1337
+ WHERE ad_group_asset.asset = 'customers/${cleanId}/assets/${args.old_asset_id}'
1338
+ AND ad_group_asset.field_type = 'SITELINK'
1339
+ AND ad_group_asset.status = 'ENABLED'
1340
+ `),
1341
+ "replaceSitelinkUrl.queryAdGroupAssets"
1342
+ );
1343
+ const customerLinksRows = await withResilience(
1344
+ () => customer.query(`
1345
+ SELECT
1346
+ customer_asset.resource_name,
1347
+ customer_asset.field_type,
1348
+ customer_asset.status
1349
+ FROM customer_asset
1350
+ WHERE customer_asset.asset = 'customers/${cleanId}/assets/${args.old_asset_id}'
1351
+ AND customer_asset.field_type = 'SITELINK'
1352
+ AND customer_asset.status = 'ENABLED'
1353
+ `),
1354
+ "replaceSitelinkUrl.queryCustomerAssets"
1355
+ );
1356
+ const campaignLinks = campaignLinksRows.map((r) => ({
1357
+ resource_name: r.campaign_asset.resource_name,
1358
+ campaign: r.campaign_asset.campaign
1359
+ }));
1360
+ const adGroupLinks = adGroupLinksRows.map((r) => ({
1361
+ resource_name: r.ad_group_asset.resource_name,
1362
+ ad_group: r.ad_group_asset.ad_group
1363
+ }));
1364
+ const customerLinks = customerLinksRows.map((r) => ({
1365
+ resource_name: r.customer_asset.resource_name
1366
+ }));
1367
+ const newSitelink = { link_text };
1368
+ if (description1) newSitelink.description1 = description1;
1369
+ if (description2) newSitelink.description2 = description2;
1370
+ const createResult = await withResilience(
1371
+ () => customer.assets.create([
1372
+ {
1373
+ type: enums.AssetType.SITELINK,
1374
+ final_urls: args.new_final_urls,
1375
+ sitelink_asset: newSitelink
1376
+ }
1377
+ ]),
1378
+ "replaceSitelinkUrl.createAsset"
1379
+ );
1380
+ const newAssetResource = createResult.results?.[0]?.resource_name;
1381
+ if (!newAssetResource) {
1382
+ throw new Error("Failed to create replacement sitelink asset (no resource_name in response)");
1383
+ }
1384
+ const newAssetId = newAssetResource.split("/").pop() || "";
1385
+ await this.autoLabelCreated(customerId, [newAssetResource], "asset");
1386
+ const SITELINK_FIELD_TYPE = enums.AssetFieldType.SITELINK;
1387
+ if (campaignLinks.length > 0) {
1388
+ await withResilience(
1389
+ () => customer.campaignAssets.create(
1390
+ campaignLinks.map((l) => ({
1391
+ campaign: l.campaign,
1392
+ asset: newAssetResource,
1393
+ field_type: SITELINK_FIELD_TYPE
1394
+ }))
1395
+ ),
1396
+ "replaceSitelinkUrl.createCampaignAssets"
1397
+ );
1398
+ }
1399
+ if (adGroupLinks.length > 0) {
1400
+ await withResilience(
1401
+ () => customer.adGroupAssets.create(
1402
+ adGroupLinks.map((l) => ({
1403
+ ad_group: l.ad_group,
1404
+ asset: newAssetResource,
1405
+ field_type: SITELINK_FIELD_TYPE
1406
+ }))
1407
+ ),
1408
+ "replaceSitelinkUrl.createAdGroupAssets"
1409
+ );
1410
+ }
1411
+ if (customerLinks.length > 0) {
1412
+ await withResilience(
1413
+ () => customer.customerAssets.create(
1414
+ customerLinks.map(() => ({
1415
+ asset: newAssetResource,
1416
+ field_type: SITELINK_FIELD_TYPE
1417
+ }))
1418
+ ),
1419
+ "replaceSitelinkUrl.createCustomerAssets"
1420
+ );
1421
+ }
1422
+ if (campaignLinks.length > 0) {
1423
+ await withResilience(
1424
+ () => customer.campaignAssets.remove(campaignLinks.map((l) => l.resource_name)),
1425
+ "replaceSitelinkUrl.removeCampaignAssets"
1426
+ );
1427
+ }
1428
+ if (adGroupLinks.length > 0) {
1429
+ await withResilience(
1430
+ () => customer.adGroupAssets.remove(adGroupLinks.map((l) => l.resource_name)),
1431
+ "replaceSitelinkUrl.removeAdGroupAssets"
1432
+ );
1433
+ }
1434
+ if (customerLinks.length > 0) {
1435
+ await withResilience(
1436
+ () => customer.customerAssets.remove(customerLinks.map((l) => l.resource_name)),
1437
+ "replaceSitelinkUrl.removeCustomerAssets"
1438
+ );
1439
+ }
1440
+ return {
1441
+ customer_id: cleanId,
1442
+ old_asset_id: args.old_asset_id,
1443
+ new_asset_id: newAssetId,
1444
+ new_asset_resource_name: newAssetResource,
1445
+ link_text,
1446
+ final_urls: args.new_final_urls,
1447
+ description1,
1448
+ description2,
1449
+ relinked: {
1450
+ campaign_assets: campaignLinks.length,
1451
+ ad_group_assets: adGroupLinks.length,
1452
+ customer_assets: customerLinks.length
1453
+ },
1454
+ note: "The old Asset is not deleted; only its ENABLED links were migrated. Paused/removed links on the old asset were left alone."
1455
+ };
1456
+ }
1457
+ // ============================================
1243
1458
  // REPORTING METHODS
1244
1459
  // ============================================
1245
1460
  // Get keyword performance report
@@ -2444,6 +2659,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2444
2659
  const result = await adsManager.updateAssetUrls(customerId, normalized.updates);
2445
2660
  return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }, null, 2) }] };
2446
2661
  }
2662
+ case "google_ads_create_sitelink": {
2663
+ const normalized = normalizeCreateSitelinkArgs(args);
2664
+ if ("error" in normalized) {
2665
+ return { content: [{ type: "text", text: JSON.stringify({ error: normalized.error }, null, 2) }] };
2666
+ }
2667
+ if (!normalized.confirm) {
2668
+ return { content: [{ type: "text", text: JSON.stringify(buildCreateSitelinkDryRun(normalized), null, 2) }] };
2669
+ }
2670
+ const customerId = normalized.customer_id || "";
2671
+ const result = await adsManager.createSitelink(customerId, {
2672
+ link_text: normalized.link_text,
2673
+ final_urls: normalized.final_urls,
2674
+ description1: normalized.description1,
2675
+ description2: normalized.description2
2676
+ });
2677
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }, null, 2) }] };
2678
+ }
2679
+ case "google_ads_replace_sitelink_url": {
2680
+ const normalized = normalizeReplaceSitelinkArgs(args);
2681
+ if ("error" in normalized) {
2682
+ return { content: [{ type: "text", text: JSON.stringify({ error: normalized.error }, null, 2) }] };
2683
+ }
2684
+ if (!normalized.confirm) {
2685
+ return { content: [{ type: "text", text: JSON.stringify(buildReplaceSitelinkDryRun(normalized), null, 2) }] };
2686
+ }
2687
+ const customerId = normalized.customer_id || "";
2688
+ const result = await adsManager.replaceSitelinkUrl(customerId, {
2689
+ old_asset_id: normalized.old_asset_id,
2690
+ new_final_urls: normalized.new_final_urls,
2691
+ new_link_text: normalized.new_link_text,
2692
+ new_description1: normalized.new_description1,
2693
+ new_description2: normalized.new_description2
2694
+ });
2695
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }, null, 2) }] };
2696
+ }
2447
2697
  case "google_ads_pause_asset_links": {
2448
2698
  const normalized = normalizePauseAssetLinksArgs(args);
2449
2699
  if ("error" in normalized) {
package/dist/tools.js CHANGED
@@ -748,6 +748,55 @@ const tools = [
748
748
  required: ["updates"]
749
749
  }
750
750
  },
751
+ {
752
+ name: "google_ads_create_sitelink",
753
+ description: "Create a new sitelink Asset (link_text + final_urls, optional two description lines). Sitelinks are shared across campaigns/ad groups -- use google_ads_replace_sitelink_url if the goal is to fix a broken URL on an existing sitelink (sitelink final_urls are immutable; the correct pattern is create new + re-link). DRY-RUN BY DEFAULT: omit `confirm` or pass `confirm: false` to preview.",
754
+ inputSchema: {
755
+ additionalProperties: false,
756
+ type: "object",
757
+ properties: {
758
+ customer_id: { type: "string" },
759
+ link_text: { type: "string", description: "Clickable sitelink label (max 25 chars)." },
760
+ final_urls: {
761
+ type: "array",
762
+ items: { type: "string" },
763
+ description: "Destination URLs. Must start with http:// or https://."
764
+ },
765
+ description1: { type: "string", description: "Optional description line 1 (max 35 chars). If set, description2 must also be set." },
766
+ description2: { type: "string", description: "Optional description line 2 (max 35 chars). If set, description1 must also be set." },
767
+ confirm: {
768
+ type: "boolean",
769
+ description: "Must be true to actually create. Omit or false for dry-run preview."
770
+ }
771
+ },
772
+ required: ["link_text", "final_urls"]
773
+ }
774
+ },
775
+ {
776
+ name: "google_ads_replace_sitelink_url",
777
+ description: "Fix a broken/outdated sitelink URL. Google Ads treats sitelink Asset.final_urls as immutable, so this creates a new sitelink asset with the corrected URL (preserving link_text + descriptions from the old one unless overridden), re-links every campaign / ad-group / customer-level attachment to the new asset, then removes the old links. The old Asset itself is NOT deleted. DRY-RUN BY DEFAULT.",
778
+ inputSchema: {
779
+ additionalProperties: false,
780
+ type: "object",
781
+ properties: {
782
+ customer_id: { type: "string" },
783
+ old_asset_id: { type: "string", description: "Numeric ID of the existing sitelink asset to replace." },
784
+ new_final_urls: {
785
+ type: "array",
786
+ items: { type: "string" },
787
+ description: "New destination URLs for the replacement sitelink. Must start with http:// or https://."
788
+ },
789
+ new_link_text: { type: "string", description: "Optional override for the sitelink click text (default: preserve from old asset). Max 25 chars." },
790
+ new_description1: { type: "string", description: "Optional override for description line 1 (default: preserve from old asset). Max 35 chars." },
791
+ new_description2: { type: "string", description: "Optional override for description line 2 (default: preserve from old asset). Max 35 chars." },
792
+ confirm: {
793
+ type: "boolean",
794
+ description: "Must be true to actually replace. Omit or false for dry-run preview."
795
+ }
796
+ },
797
+ required: ["old_asset_id", "new_final_urls"]
798
+ }
799
+ },
751
800
  {
752
801
  name: "google_ads_pause_asset_links",
753
802
  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.",
package/dist/writeGate.js CHANGED
@@ -22,7 +22,9 @@ const WRITE_TOOLS = /* @__PURE__ */ new Set([
22
22
  "google_ads_update_campaign_tracking",
23
23
  "google_ads_update_campaign_budget",
24
24
  "google_ads_update_campaign_bidding",
25
- "google_ads_update_asset_urls"
25
+ "google_ads_update_asset_urls",
26
+ "google_ads_create_sitelink",
27
+ "google_ads_replace_sitelink_url"
26
28
  ]);
27
29
  function isWriteTool(name) {
28
30
  return WRITE_TOOLS.has(name);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "mcp-google-ads",
3
3
  "mcpName": "io.github.mharnett/google-ads",
4
- "version": "1.4.2",
5
- "description": "MCP server for Google Ads API with MCC support, 41 tools for campaign management, reporting, and optimization. Read-only by default -- mutating tools require GOOGLE_ADS_MCP_WRITE=true. All creates/updates land PAUSED.",
4
+ "version": "1.4.4",
5
+ "description": "MCP server for Google Ads API with MCC support, 43 tools for campaign management, reporting, and optimization. Read-only by default -- mutating tools require GOOGLE_ADS_MCP_WRITE=true. All creates/updates land PAUSED.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "mcp-google-ads": "dist/index.js",