mcp-google-ads 1.0.12 → 1.0.13

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.
@@ -1 +1 @@
1
- {"sha":"9ce7045","builtAt":"2026-04-09T22:09:13.575Z"}
1
+ {"sha":"00a842b","builtAt":"2026-04-09T22:23:35.553Z"}
package/dist/errors.js CHANGED
@@ -37,6 +37,11 @@ export function validateCredentials() {
37
37
  "GOOGLE_ADS_REFRESH_TOKEN",
38
38
  ];
39
39
  const missing = required.filter((key) => !process.env[key] || process.env[key].trim() === "");
40
+ // Basic format validation: credentials should have reasonable length > 10 chars
41
+ const malformed = required.filter((key) => process.env[key] && process.env[key].trim().length > 0 && process.env[key].trim().length < 10);
42
+ if (malformed.length > 0) {
43
+ missing.push(...malformed.map(k => `${k} (format: too short, expected length > 10)`));
44
+ }
40
45
  return { valid: missing.length === 0, missing };
41
46
  }
42
47
  /**
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
8
8
  import { tools } from "./tools.js";
9
9
  import { GoogleAdsApi, enums } from "google-ads-api";
10
10
  import { readFileSync, existsSync } from "fs";
11
+ import v8 from "v8";
11
12
  // CLI package info
12
13
  const __cliPkg = JSON.parse(readFileSync(join(dirname(new URL(import.meta.url).pathname), "..", "package.json"), "utf-8"));
13
14
  // Log build fingerprint at startup
@@ -39,6 +40,15 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
39
40
  console.error(__cliPkg.version);
40
41
  process.exit(0);
41
42
  }
43
+ // Startup: detect npx vs direct node
44
+ if (process.argv[1]?.includes('.npm/_npx')) {
45
+ console.error("[startup] Running via npx -- first run may be slow due to package resolution");
46
+ }
47
+ // Startup: check heap size
48
+ const heapLimit = v8.getHeapStatistics().heap_size_limit;
49
+ if (heapLimit < 256 * 1024 * 1024) {
50
+ console.error(`[startup] WARNING: Heap limit is ${Math.round(heapLimit / 1024 / 1024)}MB`);
51
+ }
42
52
  // ============================================
43
53
  // ENV VAR TRIMMING
44
54
  // ============================================
@@ -96,9 +106,9 @@ function getClientFromWorkingDir(config, cwd) {
96
106
  function sanitizeNumericId(id) {
97
107
  return id.replace(/[^0-9]/g, "");
98
108
  }
99
- /** Escape single quotes in strings used in GAQL WHERE clauses. */
109
+ /** Escape single quotes and backslashes in strings used in GAQL WHERE clauses. */
100
110
  function escapeGaqlString(s) {
101
- return s.replace(/'/g, "\\'");
111
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
102
112
  }
103
113
  // ============================================
104
114
  // TYPED ERRORS & VALIDATION (extracted to errors.ts)
@@ -315,12 +325,8 @@ class GoogleAdsManager {
315
325
  const customer = this.getCustomer(customerId);
316
326
  const cleanId = customerId.replace(/-/g, "");
317
327
  // Check if label already exists
318
- const existing = await withResilience(() => customer.query(`
319
- SELECT label.resource_name, label.name
320
- FROM label
321
- WHERE label.name = '${escapeGaqlString(labelName)}'
322
- AND label.status = 'ENABLED'
323
- `), "ensureLabelExists.query");
328
+ const safeName = escapeGaqlString(labelName);
329
+ const existing = await withResilience(() => customer.query(`SELECT label.resource_name, label.name FROM label WHERE label.name = '` + safeName + `' AND label.status = 'ENABLED'`), "ensureLabelExists.query");
324
330
  if (existing.length > 0) {
325
331
  return existing[0].label.resource_name;
326
332
  }
@@ -328,9 +334,7 @@ class GoogleAdsManager {
328
334
  const result = await this.createLabel(customerId, labelName);
329
335
  if (result.existing) {
330
336
  // Race condition: re-query
331
- const requery = await withResilience(() => customer.query(`
332
- SELECT label.resource_name FROM label WHERE label.name = '${escapeGaqlString(labelName)}' AND label.status = 'ENABLED'
333
- `), "ensureLabelExists.requery");
337
+ const requery = await withResilience(() => customer.query(`SELECT label.resource_name FROM label WHERE label.name = '` + safeName + `' AND label.status = 'ENABLED'`), "ensureLabelExists.requery");
334
338
  return requery[0].label.resource_name;
335
339
  }
336
340
  return result.results[0].resource_name;
@@ -1011,37 +1015,18 @@ class GoogleAdsManager {
1011
1015
  // Get search term category insights for a campaign (with trend comparison)
1012
1016
  async getSearchTermInsights(customerId, options) {
1013
1017
  const customer = this.getCustomer(customerId);
1018
+ const safeCampaignId = sanitizeNumericId(options.campaignId);
1019
+ const safeStartDate = escapeGaqlString(options.startDate);
1020
+ const safeEndDate = escapeGaqlString(options.endDate);
1014
1021
  // Current period - get categories with metrics
1015
- const currentQuery = `
1016
- SELECT
1017
- campaign_search_term_insight.campaign_id,
1018
- campaign_search_term_insight.category_label,
1019
- campaign_search_term_insight.id,
1020
- metrics.clicks,
1021
- metrics.impressions,
1022
- metrics.conversions,
1023
- metrics.conversions_value
1024
- FROM campaign_search_term_insight
1025
- WHERE campaign_search_term_insight.campaign_id = '${options.campaignId}'
1026
- AND segments.date BETWEEN '${options.startDate}' AND '${options.endDate}'
1027
- `;
1022
+ const currentQuery = `SELECT campaign_search_term_insight.campaign_id, campaign_search_term_insight.category_label, campaign_search_term_insight.id, metrics.clicks, metrics.impressions, metrics.conversions, metrics.conversions_value FROM campaign_search_term_insight WHERE campaign_search_term_insight.campaign_id = '` + safeCampaignId + `' AND segments.date BETWEEN '` + safeStartDate + `' AND '` + safeEndDate + `'`;
1028
1023
  const currentResults = await withResilience(() => customer.query(currentQuery), "getSearchTermInsights.current");
1029
1024
  // If comparison dates provided, get previous period too
1030
1025
  let previousResults = [];
1031
1026
  if (options.compareStartDate && options.compareEndDate) {
1032
- const prevQuery = `
1033
- SELECT
1034
- campaign_search_term_insight.campaign_id,
1035
- campaign_search_term_insight.category_label,
1036
- campaign_search_term_insight.id,
1037
- metrics.clicks,
1038
- metrics.impressions,
1039
- metrics.conversions,
1040
- metrics.conversions_value
1041
- FROM campaign_search_term_insight
1042
- WHERE campaign_search_term_insight.campaign_id = '${options.campaignId}'
1043
- AND segments.date BETWEEN '${options.compareStartDate}' AND '${options.compareEndDate}'
1044
- `;
1027
+ const safeCompStart = escapeGaqlString(options.compareStartDate);
1028
+ const safeCompEnd = escapeGaqlString(options.compareEndDate);
1029
+ const prevQuery = `SELECT campaign_search_term_insight.campaign_id, campaign_search_term_insight.category_label, campaign_search_term_insight.id, metrics.clicks, metrics.impressions, metrics.conversions, metrics.conversions_value FROM campaign_search_term_insight WHERE campaign_search_term_insight.campaign_id = '` + safeCampaignId + `' AND segments.date BETWEEN '` + safeCompStart + `' AND '` + safeCompEnd + `'`;
1045
1030
  previousResults = await withResilience(() => customer.query(prevQuery), "getSearchTermInsights.previous");
1046
1031
  }
1047
1032
  // Build previous period lookup by category label
@@ -1110,21 +1095,11 @@ class GoogleAdsManager {
1110
1095
  // Get individual search terms within a specific insight category
1111
1096
  async getSearchTermInsightTerms(customerId, options) {
1112
1097
  const customer = this.getCustomer(customerId);
1113
- const query = `
1114
- SELECT
1115
- campaign_search_term_insight.campaign_id,
1116
- campaign_search_term_insight.category_label,
1117
- campaign_search_term_insight.id,
1118
- segments.search_term,
1119
- metrics.clicks,
1120
- metrics.impressions,
1121
- metrics.conversions,
1122
- metrics.conversions_value
1123
- FROM campaign_search_term_insight
1124
- WHERE campaign_search_term_insight.campaign_id = '${options.campaignId}'
1125
- AND campaign_search_term_insight.id = '${options.insightId}'
1126
- AND segments.date BETWEEN '${options.startDate}' AND '${options.endDate}'
1127
- `;
1098
+ const safeCampId = sanitizeNumericId(options.campaignId);
1099
+ const safeInsId = sanitizeNumericId(options.insightId);
1100
+ const safeStart = escapeGaqlString(options.startDate);
1101
+ const safeEnd = escapeGaqlString(options.endDate);
1102
+ const query = `SELECT campaign_search_term_insight.campaign_id, campaign_search_term_insight.category_label, campaign_search_term_insight.id, segments.search_term, metrics.clicks, metrics.impressions, metrics.conversions, metrics.conversions_value FROM campaign_search_term_insight WHERE campaign_search_term_insight.campaign_id = '` + safeCampId + `' AND campaign_search_term_insight.id = '` + safeInsId + `' AND segments.date BETWEEN '` + safeStart + `' AND '` + safeEnd + `'`;
1128
1103
  const result = await withResilience(() => customer.query(query), "getSearchTermInsightTerms");
1129
1104
  return safeResponse(result, "getSearchTermInsightTerms");
1130
1105
  }
@@ -5,6 +5,7 @@ import pino from "pino";
5
5
  // ============================================
6
6
  export const logger = pino({
7
7
  level: process.env.LOG_LEVEL || "info",
8
+ redact: ["access_token", "refresh_token", "client_secret", "*.access_token", "*.refresh_token", "*.client_secret"],
8
9
  ...(process.env.NODE_ENV !== "test" && process.stderr.isTTY && {
9
10
  transport: {
10
11
  target: "pino-pretty",
package/dist/tools.js CHANGED
@@ -3,6 +3,7 @@ export const tools = [
3
3
  name: "google_ads_get_client_context",
4
4
  description: "Get the current client context and health status based on working directory. Call this first to confirm which Google Ads account you're working with.",
5
5
  inputSchema: {
6
+ additionalProperties: false,
6
7
  type: "object",
7
8
  properties: {
8
9
  working_directory: {
@@ -17,6 +18,7 @@ export const tools = [
17
18
  name: "google_ads_list_campaigns",
18
19
  description: "List all campaigns for the current client account",
19
20
  inputSchema: {
21
+ additionalProperties: false,
20
22
  type: "object",
21
23
  properties: {
22
24
  customer_id: {
@@ -30,6 +32,7 @@ export const tools = [
30
32
  name: "google_ads_list_ad_groups",
31
33
  description: "List ad groups, optionally filtered by campaign",
32
34
  inputSchema: {
35
+ additionalProperties: false,
33
36
  type: "object",
34
37
  properties: {
35
38
  customer_id: { type: "string" },
@@ -41,10 +44,11 @@ export const tools = [
41
44
  name: "google_ads_get_campaign_tracking",
42
45
  description: "Get campaign tracking parameters including tracking URL template, final URL suffix, and custom URL parameters",
43
46
  inputSchema: {
47
+ additionalProperties: false,
44
48
  type: "object",
45
49
  properties: {
46
50
  customer_id: { type: "string" },
47
- campaign_id: { type: "string", description: "The campaign ID to get tracking info for" },
51
+ campaign_id: { type: "string", description: "The numeric string campaign ID to get tracking info for" },
48
52
  },
49
53
  required: ["campaign_id"],
50
54
  },
@@ -53,6 +57,7 @@ export const tools = [
53
57
  name: "google_ads_list_pending_changes",
54
58
  description: "List all pending changes (paused items with claude- label) awaiting review",
55
59
  inputSchema: {
60
+ additionalProperties: false,
56
61
  type: "object",
57
62
  properties: {
58
63
  customer_id: { type: "string" },
@@ -63,6 +68,7 @@ export const tools = [
63
68
  name: "google_ads_validate_ad",
64
69
  description: "Validate an RSA without creating it. Use this to check for errors before creating.",
65
70
  inputSchema: {
71
+ additionalProperties: false,
66
72
  type: "object",
67
73
  properties: {
68
74
  headlines: {
@@ -87,6 +93,7 @@ export const tools = [
87
93
  name: "google_ads_create_campaign",
88
94
  description: "Create a new campaign (will be PAUSED until approved). Returns campaign ID.",
89
95
  inputSchema: {
96
+ additionalProperties: false,
90
97
  type: "object",
91
98
  properties: {
92
99
  customer_id: { type: "string" },
@@ -100,6 +107,7 @@ export const tools = [
100
107
  name: "google_ads_create_ad_group",
101
108
  description: "Create a new ad group (will be PAUSED until approved). Returns ad group ID.",
102
109
  inputSchema: {
110
+ additionalProperties: false,
103
111
  type: "object",
104
112
  properties: {
105
113
  customer_id: { type: "string" },
@@ -114,6 +122,7 @@ export const tools = [
114
122
  name: "google_ads_create_responsive_search_ad",
115
123
  description: "Create a responsive search ad (will be PAUSED until approved). Validates before creating. Headlines/descriptions can be plain strings or objects with pinned_position (1-3 for headlines, 1-2 for descriptions).",
116
124
  inputSchema: {
125
+ additionalProperties: false,
117
126
  type: "object",
118
127
  properties: {
119
128
  customer_id: { type: "string" },
@@ -161,6 +170,7 @@ export const tools = [
161
170
  name: "google_ads_create_keywords",
162
171
  description: "Create keywords for an ad group (will be PAUSED until approved). Keywords are auto-labeled for easy discovery via google_ads_list_pending_changes.",
163
172
  inputSchema: {
173
+ additionalProperties: false,
164
174
  type: "object",
165
175
  properties: {
166
176
  customer_id: { type: "string" },
@@ -188,6 +198,7 @@ export const tools = [
188
198
  name: "google_ads_enable_items",
189
199
  description: "Enable paused campaigns, ad groups, or ads. REQUIRES USER APPROVAL. Use after reviewing in Google Ads UI.",
190
200
  inputSchema: {
201
+ additionalProperties: false,
191
202
  type: "object",
192
203
  properties: {
193
204
  customer_id: { type: "string" },
@@ -201,6 +212,7 @@ export const tools = [
201
212
  name: "google_ads_pause_items",
202
213
  description: "Pause enabled campaigns, ad groups, or ads. REQUIRES USER APPROVAL. This will stop items from serving.",
203
214
  inputSchema: {
215
+ additionalProperties: false,
204
216
  type: "object",
205
217
  properties: {
206
218
  customer_id: { type: "string" },
@@ -214,6 +226,7 @@ export const tools = [
214
226
  name: "google_ads_create_shared_set",
215
227
  description: "Create a new shared negative keyword list at account level. Returns the new shared set ID.",
216
228
  inputSchema: {
229
+ additionalProperties: false,
217
230
  type: "object",
218
231
  properties: {
219
232
  customer_id: { type: "string" },
@@ -226,11 +239,12 @@ export const tools = [
226
239
  name: "google_ads_link_shared_set",
227
240
  description: "Link a shared negative keyword list to one or more campaigns. Once linked, all negatives in the list will apply to those campaigns.",
228
241
  inputSchema: {
242
+ additionalProperties: false,
229
243
  type: "object",
230
244
  properties: {
231
245
  customer_id: { type: "string" },
232
246
  shared_set_id: { type: "string", description: "The shared set ID to link" },
233
- campaign_ids: { type: "array", items: { type: "string" }, description: "Campaign IDs to link the shared set to" },
247
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Numeric string campaign IDs to link the shared set to" },
234
248
  },
235
249
  required: ["shared_set_id", "campaign_ids"],
236
250
  },
@@ -239,11 +253,12 @@ export const tools = [
239
253
  name: "google_ads_unlink_shared_set",
240
254
  description: "Unlink a shared negative keyword list from one or more campaigns. The list's negatives will no longer apply to those campaigns.",
241
255
  inputSchema: {
256
+ additionalProperties: false,
242
257
  type: "object",
243
258
  properties: {
244
259
  customer_id: { type: "string" },
245
260
  shared_set_id: { type: "string", description: "The shared set ID to unlink" },
246
- campaign_ids: { type: "array", items: { type: "string" }, description: "Campaign IDs to unlink the shared set from" },
261
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Numeric string campaign IDs to unlink the shared set from" },
247
262
  },
248
263
  required: ["shared_set_id", "campaign_ids"],
249
264
  },
@@ -252,6 +267,7 @@ export const tools = [
252
267
  name: "google_ads_add_shared_negatives",
253
268
  description: "Add negative keywords to a shared negative keyword list. Keywords will immediately block matching queries across all campaigns the list is applied to.",
254
269
  inputSchema: {
270
+ additionalProperties: false,
255
271
  type: "object",
256
272
  properties: {
257
273
  customer_id: { type: "string" },
@@ -275,6 +291,7 @@ export const tools = [
275
291
  name: "google_ads_remove_shared_negatives",
276
292
  description: "Remove negative keywords from a shared negative keyword list by their resource names. Get resource names from a GAQL query on shared_criterion.",
277
293
  inputSchema: {
294
+ additionalProperties: false,
278
295
  type: "object",
279
296
  properties: {
280
297
  customer_id: { type: "string" },
@@ -291,6 +308,7 @@ export const tools = [
291
308
  name: "google_ads_add_campaign_negatives",
292
309
  description: "Add negative keywords at the campaign level. Use for campaign-specific negatives that shouldn't be in a shared list.",
293
310
  inputSchema: {
311
+ additionalProperties: false,
294
312
  type: "object",
295
313
  properties: {
296
314
  customer_id: { type: "string" },
@@ -314,6 +332,7 @@ export const tools = [
314
332
  name: "google_ads_remove_campaign_negatives",
315
333
  description: "Remove campaign-level negative keywords by their resource names. Get resource names from a GAQL query on campaign_criterion.",
316
334
  inputSchema: {
335
+ additionalProperties: false,
317
336
  type: "object",
318
337
  properties: {
319
338
  customer_id: { type: "string" },
@@ -330,6 +349,7 @@ export const tools = [
330
349
  name: "google_ads_remove_adgroup_negatives",
331
350
  description: "Remove ad-group-level negative keywords by their resource names. Get resource names from a GAQL query on ad_group_criterion.",
332
351
  inputSchema: {
352
+ additionalProperties: false,
333
353
  type: "object",
334
354
  properties: {
335
355
  customer_id: { type: "string" },
@@ -346,6 +366,7 @@ export const tools = [
346
366
  name: "google_ads_pause_keywords",
347
367
  description: "Pause active keywords by their criterion resource names.",
348
368
  inputSchema: {
369
+ additionalProperties: false,
349
370
  type: "object",
350
371
  properties: {
351
372
  customer_id: { type: "string" },
@@ -358,10 +379,11 @@ export const tools = [
358
379
  name: "google_ads_update_campaign_tracking",
359
380
  description: "Update campaign tracking parameters: final URL suffix, tracking URL template, and/or custom URL parameters. Use google_ads_get_campaign_tracking first to see current values.",
360
381
  inputSchema: {
382
+ additionalProperties: false,
361
383
  type: "object",
362
384
  properties: {
363
385
  customer_id: { type: "string" },
364
- campaign_id: { type: "string", description: "The campaign ID to update" },
386
+ campaign_id: { type: "string", description: "The numeric string campaign ID to update" },
365
387
  final_url_suffix: { type: "string", description: "New final URL suffix (appended to landing page URLs). Set to empty string to clear." },
366
388
  tracking_url_template: { type: "string", description: "New tracking URL template. Set to empty string to clear." },
367
389
  url_custom_parameters: {
@@ -387,13 +409,14 @@ export const tools = [
387
409
  name: "google_ads_keyword_performance",
388
410
  description: "Get keyword performance report with metrics including impressions, clicks, cost, conversions, quality score components (quality score, expected CTR, ad relevance, landing page experience), and impression share metrics.",
389
411
  inputSchema: {
412
+ additionalProperties: false,
390
413
  type: "object",
391
414
  properties: {
392
415
  customer_id: { type: "string" },
393
416
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
394
417
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
395
418
  keyword_text_contains: { type: "string", description: "Filter keywords containing this text" },
396
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
419
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
397
420
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
398
421
  },
399
422
  required: ["start_date", "end_date"],
@@ -403,13 +426,14 @@ export const tools = [
403
426
  name: "google_ads_keyword_performance_by_conversion",
404
427
  description: "Get keyword performance broken down by conversion action. Shows which keywords drive which conversion types (e.g., form fills, MQLs, etc.).",
405
428
  inputSchema: {
429
+ additionalProperties: false,
406
430
  type: "object",
407
431
  properties: {
408
432
  customer_id: { type: "string" },
409
433
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
410
434
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
411
435
  keyword_text_contains: { type: "string", description: "Filter keywords containing this text" },
412
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
436
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
413
437
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
414
438
  },
415
439
  required: ["start_date", "end_date"],
@@ -419,6 +443,7 @@ export const tools = [
419
443
  name: "google_ads_search_term_report",
420
444
  description: "Get search term report showing actual search queries that triggered ads, with the keyword they matched to.",
421
445
  inputSchema: {
446
+ additionalProperties: false,
422
447
  type: "object",
423
448
  properties: {
424
449
  customer_id: { type: "string" },
@@ -426,7 +451,7 @@ export const tools = [
426
451
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
427
452
  keyword_text_contains: { type: "string", description: "Filter by keyword text" },
428
453
  search_term_contains: { type: "string", description: "Filter search terms containing this text" },
429
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
454
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
430
455
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
431
456
  },
432
457
  required: ["start_date", "end_date"],
@@ -436,6 +461,7 @@ export const tools = [
436
461
  name: "google_ads_search_term_report_by_conversion",
437
462
  description: "Get search term report broken down by conversion action. Shows which search queries drive which conversion types.",
438
463
  inputSchema: {
464
+ additionalProperties: false,
439
465
  type: "object",
440
466
  properties: {
441
467
  customer_id: { type: "string" },
@@ -443,7 +469,7 @@ export const tools = [
443
469
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
444
470
  keyword_text_contains: { type: "string", description: "Filter by keyword text" },
445
471
  search_term_contains: { type: "string", description: "Filter search terms containing this text" },
446
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
472
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
447
473
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
448
474
  },
449
475
  required: ["start_date", "end_date"],
@@ -453,12 +479,13 @@ export const tools = [
453
479
  name: "google_ads_ad_performance",
454
480
  description: "Get ad performance report with metrics, ad copy (headlines/descriptions), final URLs, and ad strength rating.",
455
481
  inputSchema: {
482
+ additionalProperties: false,
456
483
  type: "object",
457
484
  properties: {
458
485
  customer_id: { type: "string" },
459
486
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
460
487
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
461
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
488
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
462
489
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
463
490
  },
464
491
  required: ["start_date", "end_date"],
@@ -468,12 +495,13 @@ export const tools = [
468
495
  name: "google_ads_ad_performance_by_conversion",
469
496
  description: "Get ad performance broken down by conversion action. Shows which ads drive which conversion types.",
470
497
  inputSchema: {
498
+ additionalProperties: false,
471
499
  type: "object",
472
500
  properties: {
473
501
  customer_id: { type: "string" },
474
502
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
475
503
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
476
- campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by campaign IDs" },
504
+ campaign_ids: { type: "array", items: { type: "string" }, description: "Filter by numeric string campaign IDs" },
477
505
  ad_group_ids: { type: "array", items: { type: "string" }, description: "Filter by ad group IDs" },
478
506
  },
479
507
  required: ["start_date", "end_date"],
@@ -483,6 +511,7 @@ export const tools = [
483
511
  name: "google_ads_list_conversion_actions",
484
512
  description: "List all available conversion actions (e.g., form fills, MQLs, etc.) to understand what conversion tracking is set up.",
485
513
  inputSchema: {
514
+ additionalProperties: false,
486
515
  type: "object",
487
516
  properties: {
488
517
  customer_id: { type: "string" },
@@ -493,10 +522,11 @@ export const tools = [
493
522
  name: "google_ads_search_term_insights",
494
523
  description: "Get search term category insights for a campaign. Shows the search categories your ads appeared against (like the Insights panel in the Google Ads UI). Must query one campaign at a time. Optionally compares two date ranges to show trends.",
495
524
  inputSchema: {
525
+ additionalProperties: false,
496
526
  type: "object",
497
527
  properties: {
498
528
  customer_id: { type: "string" },
499
- campaign_id: { type: "string", description: "The campaign ID to get insights for (required, one campaign at a time)" },
529
+ campaign_id: { type: "string", description: "The numeric string campaign ID to get insights for (required, one campaign at a time)" },
500
530
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
501
531
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
502
532
  compare_start_date: { type: "string", description: "Comparison period start date (optional, for trend calculation)" },
@@ -509,10 +539,11 @@ export const tools = [
509
539
  name: "google_ads_search_term_insight_terms",
510
540
  description: "Drill into a specific search term category to see the individual search terms within it. Requires an insight_id from google_ads_search_term_insights.",
511
541
  inputSchema: {
542
+ additionalProperties: false,
512
543
  type: "object",
513
544
  properties: {
514
545
  customer_id: { type: "string" },
515
- campaign_id: { type: "string", description: "The campaign ID" },
546
+ campaign_id: { type: "string", description: "The numeric string campaign ID" },
516
547
  insight_id: { type: "string", description: "The insight category ID from google_ads_search_term_insights results" },
517
548
  start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
518
549
  end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
@@ -524,10 +555,11 @@ export const tools = [
524
555
  name: "google_ads_update_campaign_budget",
525
556
  description: "Update the daily budget for a campaign. Can update the existing budget amount or create a new solo budget and reassign the campaign to it (useful for breaking shared budgets).",
526
557
  inputSchema: {
558
+ additionalProperties: false,
527
559
  type: "object",
528
560
  properties: {
529
561
  customer_id: { type: "string" },
530
- campaign_id: { type: "string", description: "The campaign ID to update" },
562
+ campaign_id: { type: "string", description: "The numeric string campaign ID to update" },
531
563
  daily_budget: { type: "number", description: "New daily budget in dollars" },
532
564
  create_new_budget: { type: "boolean", description: "If true, creates a new budget and reassigns this campaign to it (breaks shared budgets). If false, updates the existing budget amount in place (affects all campaigns sharing it)." },
533
565
  },
@@ -538,6 +570,7 @@ export const tools = [
538
570
  name: "google_ads_gaql_query",
539
571
  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.",
540
572
  inputSchema: {
573
+ additionalProperties: false,
541
574
  type: "object",
542
575
  properties: {
543
576
  customer_id: { type: "string" },
@@ -550,6 +583,7 @@ export const tools = [
550
583
  name: "google_ads_keyword_volume",
551
584
  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.",
552
585
  inputSchema: {
586
+ additionalProperties: false,
553
587
  type: "object",
554
588
  properties: {
555
589
  customer_id: { type: "string", description: "The Google Ads customer ID" },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-google-ads",
3
3
  "mcpName": "io.github.mharnett/google-ads",
4
- "version": "1.0.12",
4
+ "version": "1.0.13",
5
5
  "description": "MCP server for Google Ads API with MCC support, 34 tools for campaign management, reporting, and optimization. Safe by default -- all changes created PAUSED.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {