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.
@@ -5,7 +5,9 @@ const MIN_DESCRIPTIONS = 2;
5
5
  const MAX_DESCRIPTIONS = 4;
6
6
  const MAX_DESCRIPTION_LENGTH = 90;
7
7
  const MAX_PATH_LENGTH = 15;
8
- const CUSTOMIZER_PATTERN = /\{CUSTOMIZER\.[^}]+\}/g;
8
+ const KEYWORD_INSERT_PATTERN = /\{(?:KeyWord|Keyword|keyword|KEYWORD):([^}]*)\}/g;
9
+ const CUSTOMIZER_WITH_DEFAULT = /\{CUSTOMIZER\.[^:}]+:([^}]*)\}/g;
10
+ const CUSTOMIZER_NO_DEFAULT = /\{CUSTOMIZER\.[^:}]+\}/g;
9
11
  const CUSTOMIZER_RENDER_LEN = 16;
10
12
  function validateRsa(ad) {
11
13
  const errors = [];
@@ -16,8 +18,9 @@ function validateRsa(ad) {
16
18
  errors.push(`Maximum ${MAX_HEADLINES} headlines, got ${ad.headlines.length}`);
17
19
  }
18
20
  (ad.headlines ?? []).forEach((h, i) => {
19
- if (h.length > MAX_HEADLINE_LENGTH) {
20
- errors.push(`Headline ${i + 1} too long (${h.length}/${MAX_HEADLINE_LENGTH}): "${h}"`);
21
+ const effectiveLen = effectiveTextLength(h);
22
+ if (effectiveLen > MAX_HEADLINE_LENGTH) {
23
+ errors.push(`Headline ${i + 1} too long (${effectiveLen}/${MAX_HEADLINE_LENGTH}): "${h}"`);
21
24
  }
22
25
  });
23
26
  if (!ad.descriptions || ad.descriptions.length < MIN_DESCRIPTIONS) {
@@ -29,7 +32,7 @@ function validateRsa(ad) {
29
32
  errors.push(`Maximum ${MAX_DESCRIPTIONS} descriptions, got ${ad.descriptions.length}`);
30
33
  }
31
34
  (ad.descriptions ?? []).forEach((d, i) => {
32
- const effectiveLen = effectiveDescriptionLength(d);
35
+ const effectiveLen = effectiveTextLength(d);
33
36
  if (effectiveLen > MAX_DESCRIPTION_LENGTH) {
34
37
  errors.push(
35
38
  `Description ${i + 1} too long (${effectiveLen}/${MAX_DESCRIPTION_LENGTH}): "${d}"`
@@ -41,13 +44,19 @@ function validateRsa(ad) {
41
44
  }
42
45
  if (!isNonEmpty(ad.path1)) {
43
46
  errors.push("path1 is required (display URL path segment)");
44
- } else if (ad.path1.length > MAX_PATH_LENGTH) {
45
- errors.push(`path1 too long (${ad.path1.length}/${MAX_PATH_LENGTH}): "${ad.path1}"`);
47
+ } else {
48
+ const effectiveLen = effectiveTextLength(ad.path1);
49
+ if (effectiveLen > MAX_PATH_LENGTH) {
50
+ errors.push(`path1 too long (${effectiveLen}/${MAX_PATH_LENGTH}): "${ad.path1}"`);
51
+ }
46
52
  }
47
53
  if (!isNonEmpty(ad.path2)) {
48
54
  errors.push("path2 is required (display URL path segment)");
49
- } else if (ad.path2.length > MAX_PATH_LENGTH) {
50
- errors.push(`path2 too long (${ad.path2.length}/${MAX_PATH_LENGTH}): "${ad.path2}"`);
55
+ } else {
56
+ const effectiveLen = effectiveTextLength(ad.path2);
57
+ if (effectiveLen > MAX_PATH_LENGTH) {
58
+ errors.push(`path2 too long (${effectiveLen}/${MAX_PATH_LENGTH}): "${ad.path2}"`);
59
+ }
51
60
  }
52
61
  const nonEmptyLabels = (ad.labels ?? []).filter((l) => isNonEmpty(l));
53
62
  if (nonEmptyLabels.length === 0) {
@@ -61,18 +70,14 @@ function validateRsa(ad) {
61
70
  function isNonEmpty(s) {
62
71
  return typeof s === "string" && s.trim().length > 0;
63
72
  }
64
- function effectiveDescriptionLength(desc) {
65
- let effectiveLen = desc.length;
66
- const matches = desc.match(CUSTOMIZER_PATTERN);
67
- if (matches) {
68
- for (const m of matches) {
69
- effectiveLen -= m.length;
70
- effectiveLen += CUSTOMIZER_RENDER_LEN;
71
- }
72
- }
73
- return effectiveLen;
73
+ function effectiveTextLength(text) {
74
+ let rendered = text.replace(KEYWORD_INSERT_PATTERN, "$1");
75
+ rendered = rendered.replace(CUSTOMIZER_WITH_DEFAULT, "$1");
76
+ rendered = rendered.replace(CUSTOMIZER_NO_DEFAULT, " ".repeat(CUSTOMIZER_RENDER_LEN));
77
+ return rendered.length;
74
78
  }
75
79
  export {
80
+ effectiveTextLength,
76
81
  validateRsa
77
82
  };
78
83
  //# sourceMappingURL=validateRsa.js.map
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.1.0",
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.",
4
+ "version": "1.2.2",
5
+ "description": "MCP server for Google Ads API with MCC support, 36 tools for campaign management, reporting, and optimization -- now includes Demand Gen campaign creation end-to-end. Safe by default -- all changes created PAUSED.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "mcp-google-ads": "dist/index.js",
@@ -73,6 +73,7 @@
73
73
  "zod": "^3.22.4"
74
74
  },
75
75
  "devDependencies": {
76
+ "@drak-marketing/mcp-test-harness": "^0.1.2",
76
77
  "@types/node": "^20.10.0",
77
78
  "@types/prompts": "^2.4.9",
78
79
  "esbuild": "^0.28.0",