Skip to content

Commit 62c0fc0

Browse files
authored
Merge pull request #1 from mharnett/code-task/mcp-marketing-suite
Sync installer and package-registry check with the 8-server suite
2 parents 091fdee + 7d04860 commit 62c0fc0

5 files changed

Lines changed: 149 additions & 8 deletions

File tree

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
"url": "https://github.com/mharnett/mcp-marketing-suite.git"
88
},
99
"scripts": {
10-
"test": "npm run test:shellcheck && npm run test:setup && npm run test:packages",
10+
"test": "npm run test:shellcheck && npm run test:setup && npm run test:manifest && npm run test:packages",
1111
"test:shellcheck": "shellcheck setup.sh",
1212
"test:setup": "bash tests/test-setup.sh",
13+
"test:manifest": "node tests/test-manifest-sync.js",
1314
"test:packages": "node tests/check-packages.js"
1415
},
1516
"license": "MIT"

‎setup.sh‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
set -e
33

44
# MCP Marketing Suite installer
5-
# Installs all six MCPs and prints a Claude config snippet.
5+
# Installs all eight MCPs and prints a Claude config snippet.
66

77
RED='\033[0;31m'
88
GREEN='\033[0;32m'
@@ -34,7 +34,7 @@ if command -v node &>/dev/null; then
3434
warn "Node.js $NODE_VERSION found but ≥18 is required"
3535
fi
3636
else
37-
warn "Node.js not found — skipping npm MCPs (Google Ads, Bing Ads, LinkedIn, GA4, GSC)"
37+
warn "Node.js not found — skipping npm MCPs (Google Ads, Bing Ads, LinkedIn, Reddit, GA4, GSC, GTM+GA4)"
3838
fi
3939

4040
if command -v npm &>/dev/null; then
@@ -78,7 +78,7 @@ NPM_FAILED=()
7878
if [ "$HAS_NODE" = true ] && [ "$HAS_NPM" = true ]; then
7979
hdr "Installing npm MCPs"
8080

81-
for pkg in mcp-google-ads mcp-bing-ads mcp-linkedin-ads mcp-ga4 mcp-google-gsc; do
81+
for pkg in mcp-google-ads mcp-bing-ads mcp-linkedin-ads mcp-reddit-ads mcp-ga4 mcp-google-gsc mcp-gtm-ga4; do
8282
if npm install -g "$pkg" &>/dev/null; then
8383
ok "$pkg"
8484
NPM_INSTALLED+=("$pkg")
@@ -176,6 +176,21 @@ EOF
176176
)")
177177
fi
178178

179+
if [[ " ${NPM_INSTALLED[*]} " =~ " mcp-reddit-ads " ]]; then
180+
ENTRIES+=("$(cat <<'EOF'
181+
"reddit-ads": {
182+
"command": "node",
183+
"args": ["NMBIN/mcp-reddit-ads/dist/index.js"],
184+
"env": {
185+
"REDDIT_CLIENT_ID": "YOUR_CLIENT_ID",
186+
"REDDIT_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
187+
"REDDIT_REFRESH_TOKEN": "YOUR_REFRESH_TOKEN"
188+
}
189+
}
190+
EOF
191+
)")
192+
fi
193+
179194
if [[ " ${NPM_INSTALLED[*]} " =~ " mcp-ga4 " ]]; then
180195
ENTRIES+=("$(cat <<'EOF'
181196
"ga4": {
@@ -203,6 +218,22 @@ EOF
203218
)")
204219
fi
205220

221+
if [[ " ${NPM_INSTALLED[*]} " =~ " mcp-gtm-ga4 " ]]; then
222+
ENTRIES+=("$(cat <<'EOF'
223+
"gtm-ga4": {
224+
"command": "node",
225+
"args": ["NMBIN/mcp-gtm-ga4/dist/index.js"],
226+
"env": {
227+
"GTM_ACCOUNT_ID": "YOUR_ACCOUNT_ID",
228+
"GTM_CONTAINER_ID": "YOUR_CONTAINER_ID",
229+
"GA4_PROPERTY_ID": "YOUR_PROPERTY_ID",
230+
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/service-account.json"
231+
}
232+
}
233+
EOF
234+
)")
235+
fi
236+
206237
if [[ " ${PIP_INSTALLED[*]} " =~ " meta-ads-mcp " ]]; then
207238
ENTRIES+=("$(cat <<'EOF'
208239
"meta-ads": {
@@ -240,7 +271,7 @@ hdr "Summary"
240271
TOTAL_INSTALLED=$(( ${#NPM_INSTALLED[@]} + ${#PIP_INSTALLED[@]} ))
241272
TOTAL_FAILED=$(( ${#NPM_FAILED[@]} + ${#PIP_FAILED[@]} ))
242273

243-
echo "$TOTAL_INSTALLED of 6 MCPs installed."
274+
echo "$TOTAL_INSTALLED of 8 MCPs installed."
244275

245276
if [ $TOTAL_FAILED -gt 0 ]; then
246277
echo ""

‎tests/check-packages.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ const NPM_PACKAGES = [
88
"mcp-google-ads",
99
"mcp-bing-ads",
1010
"mcp-linkedin-ads",
11+
"mcp-reddit-ads",
1112
"mcp-ga4",
1213
"mcp-google-gsc",
14+
"mcp-gtm-ga4",
1315
];
1416

1517
const PYPI_PACKAGES = ["meta-ads-mcp"];

‎tests/test-manifest-sync.js‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env node
2+
// Verifies the three places that enumerate "which servers this suite ships"
3+
// agree with each other:
4+
// 1. README.md's "The Suite" table (the human-facing source of truth)
5+
// 2. tests/check-packages.js's NPM_PACKAGES / PYPI_PACKAGES (the registry
6+
// existence check)
7+
// 3. setup.sh's npm install loop (the one-shot installer)
8+
//
9+
// These three lists are maintained by hand in three different file formats.
10+
// Each one looks correct read in isolation — the bug only shows up when you
11+
// check that they actually agree. A server added to the README table with no
12+
// matching entry in check-packages.js silently stops being verified; a
13+
// server missing from setup.sh's install loop silently stops being
14+
// installed. Exit 0 = all three agree. Exit 1 = drift detected.
15+
16+
const fs = require("fs");
17+
const path = require("path");
18+
19+
const ROOT = path.join(__dirname, "..");
20+
const README_PATH = path.join(ROOT, "README.md");
21+
const CHECK_PACKAGES_PATH = path.join(ROOT, "tests", "check-packages.js");
22+
const SETUP_SH_PATH = path.join(ROOT, "setup.sh");
23+
24+
// Hand-picked anchor: the exact suite as of this task's spec. If this list
25+
// itself needs to change, that's a deliberate suite change, not drift.
26+
const EXPECTED_NPM_PACKAGES = [
27+
"mcp-google-ads",
28+
"mcp-bing-ads",
29+
"mcp-linkedin-ads",
30+
"mcp-reddit-ads",
31+
"mcp-ga4",
32+
"mcp-google-gsc",
33+
"mcp-gtm-ga4",
34+
];
35+
const EXPECTED_PYPI_PACKAGES = ["meta-ads-mcp"];
36+
37+
function readmeInstallCommands(readme) {
38+
const npm = [...readme.matchAll(/`npm install (mcp-[a-z0-9-]+)`/g)].map((m) => m[1]);
39+
const pip = [...readme.matchAll(/`pip install ([a-z0-9-]+)`/g)].map((m) => m[1]);
40+
return { npm, pip };
41+
}
42+
43+
function checkPackagesArray(source, varName) {
44+
const re = new RegExp(`const ${varName} = \\[([^\\]]*)\\]`, "s");
45+
const match = source.match(re);
46+
if (!match) throw new Error(`Could not find ${varName} in check-packages.js`);
47+
return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
48+
}
49+
50+
function setupShNpmPackages(source) {
51+
const match = source.match(/for pkg in ([a-z0-9 -]+); do/);
52+
if (!match) throw new Error("Could not find npm install loop in setup.sh");
53+
return match[1].trim().split(/\s+/);
54+
}
55+
56+
function sorted(arr) {
57+
return [...arr].sort();
58+
}
59+
60+
let failures = 0;
61+
function assertSameMembers(label, actual, expected) {
62+
const a = sorted(actual);
63+
const e = sorted(expected);
64+
const ok = a.length === e.length && a.every((v, i) => v === e[i]);
65+
if (ok) {
66+
console.log(` ✓ ${label}`);
67+
} else {
68+
console.log(` ✗ ${label}`);
69+
console.log(` expected: ${JSON.stringify(e)}`);
70+
console.log(` actual: ${JSON.stringify(a)}`);
71+
failures++;
72+
}
73+
}
74+
75+
const readme = fs.readFileSync(README_PATH, "utf8");
76+
const checkPackagesSrc = fs.readFileSync(CHECK_PACKAGES_PATH, "utf8");
77+
const setupShSrc = fs.readFileSync(SETUP_SH_PATH, "utf8");
78+
79+
const { npm: readmeNpm, pip: readmePip } = readmeInstallCommands(readme);
80+
const scriptNpm = checkPackagesArray(checkPackagesSrc, "NPM_PACKAGES");
81+
const scriptPypi = checkPackagesArray(checkPackagesSrc, "PYPI_PACKAGES");
82+
const setupNpm = setupShNpmPackages(setupShSrc);
83+
84+
console.log("");
85+
console.log("Suite manifest sync (README ↔ check-packages.js ↔ setup.sh)");
86+
console.log("─".repeat(60));
87+
88+
// Anchors: README itself must list exactly the expected suite.
89+
assertSameMembers("README table lists the expected 7 npm packages", readmeNpm, EXPECTED_NPM_PACKAGES);
90+
assertSameMembers("README table lists the expected PyPI package", readmePip, EXPECTED_PYPI_PACKAGES);
91+
92+
// Joins: the two mirrors must agree with the README, not just look plausible on their own.
93+
assertSameMembers("check-packages.js NPM_PACKAGES matches README", scriptNpm, readmeNpm);
94+
assertSameMembers("check-packages.js PYPI_PACKAGES matches README", scriptPypi, readmePip);
95+
assertSameMembers("setup.sh install loop matches README's npm packages", setupNpm, readmeNpm);
96+
97+
console.log("");
98+
if (failures > 0) {
99+
console.error(`${failures} manifest mismatch(es) found.`);
100+
process.exit(1);
101+
} else {
102+
console.log("All manifests agree.");
103+
}

‎tests/test-setup.sh‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,16 @@ assert_contains "detects Node.js" "Node.js 20.11.0" "$out"
168168
assert_contains "uses uv" "uv found" "$out"
169169
assert_contains "installs mcp-google-ads" "✓ mcp-google-ads" "$out"
170170
assert_contains "installs mcp-bing-ads" "✓ mcp-bing-ads" "$out"
171+
assert_contains "installs mcp-reddit-ads" "✓ mcp-reddit-ads" "$out"
172+
assert_contains "installs mcp-gtm-ga4" "✓ mcp-gtm-ga4" "$out"
171173
assert_contains "installs meta-ads-mcp" "✓ meta-ads-mcp" "$out"
172174
assert_contains "config snippet present" '"mcpServers"' "$out"
173175
assert_contains "google-ads entry" '"google-ads"' "$out"
176+
assert_contains "reddit-ads entry" '"reddit-ads"' "$out"
177+
assert_contains "gtm-ga4 entry" '"gtm-ga4"' "$out"
174178
assert_contains "meta-ads entry" '"meta-ads"' "$out"
175179
assert_contains "path substituted" "/fake/prefix" "$out"
176-
assert_contains "shows 6 of 6 installed" "6 of 6 MCPs installed" "$out"
180+
assert_contains "shows 8 of 8 installed" "8 of 8 MCPs installed" "$out"
177181

178182
# ── test: pip3 used when uv absent ────────────────────────────────────────────
179183
echo ""
@@ -188,7 +192,7 @@ assert_exit "exits 0" 0 "$exit_code"
188192
assert_contains "uses pip3" "Python 3.11.0 (pip3)" "$out"
189193
assert_contains "installs meta-ads-mcp" "✓ meta-ads-mcp" "$out"
190194
assert_contains "meta-ads entry" '"meta-ads"' "$out"
191-
assert_contains "shows 6 of 6 installed" "6 of 6 MCPs installed" "$out"
195+
assert_contains "shows 8 of 8 installed" "8 of 8 MCPs installed" "$out"
192196

193197
# ── test: node not found ───────────────────────────────────────────────────────
194198
echo ""
@@ -267,7 +271,7 @@ assert_contains "flags failed package" "mcp-bing-ads (install failed)" "$ou
267271
assert_contains "google-ads still succeeds" '"google-ads"' "$out"
268272
assert_not_contains "no bing-ads entry" '"bing-ads"' "$out"
269273
assert_contains "meta-ads still installs" "✓ meta-ads-mcp" "$out"
270-
assert_contains "shows 5 of 6" "5 of 6 MCPs installed" "$out"
274+
assert_contains "shows 7 of 8" "7 of 8 MCPs installed" "$out"
271275

272276
# ── test: config snippet path substitution ─────────────────────────────────────
273277
echo ""

0 commit comments

Comments
 (0)