mcp-google-ads 1.2.4 → 1.3.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/build-info.json +2 -2
- package/dist/campaignBuilder.js +8 -0
- package/dist/doctor-cli.d.ts +12 -0
- package/dist/doctor-cli.js +144 -0
- package/dist/install-cli.d.ts +12 -0
- package/dist/install-cli.js +127 -0
- package/package.json +16 -2
package/dist/build-info.json
CHANGED
package/dist/campaignBuilder.js
CHANGED
|
@@ -19,6 +19,14 @@ function buildCampaignCreatePayload(input) {
|
|
|
19
19
|
status: enums.CampaignStatus.PAUSED,
|
|
20
20
|
advertising_channel_type: channelEnum
|
|
21
21
|
};
|
|
22
|
+
if (channelType === "DEMAND_GEN") {
|
|
23
|
+
campaign.network_settings = {
|
|
24
|
+
target_google_search: false,
|
|
25
|
+
target_search_network: false,
|
|
26
|
+
target_content_network: false,
|
|
27
|
+
target_partner_search_network: false
|
|
28
|
+
};
|
|
29
|
+
}
|
|
22
30
|
if (input.start_date) campaign.start_date = input.start_date;
|
|
23
31
|
if (input.end_date) campaign.end_date = input.end_date;
|
|
24
32
|
switch (strategy) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export interface DoctorCheck {
|
|
3
|
+
name: string;
|
|
4
|
+
status: "pass" | "fail" | "warn";
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DoctorOptions {
|
|
8
|
+
configPath?: string;
|
|
9
|
+
credentialsPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function runDoctor(opts?: DoctorOptions): Promise<DoctorCheck[]>;
|
|
12
|
+
export declare function run(argv?: string[]): Promise<number>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { credentialsFilePath } from "./credentials.js";
|
|
4
|
+
import { resolveDefaultConfigPath } from "./install-cli.js";
|
|
5
|
+
async function runDoctor(opts = {}) {
|
|
6
|
+
const cfgPath = opts.configPath ?? resolveDefaultConfigPath();
|
|
7
|
+
const credsPath = opts.credentialsPath ?? credentialsFilePath;
|
|
8
|
+
const checks = [];
|
|
9
|
+
checks.push(checkNodeVersion());
|
|
10
|
+
const cfgExists = existsSync(cfgPath);
|
|
11
|
+
checks.push({
|
|
12
|
+
name: "claude_desktop_config exists",
|
|
13
|
+
status: cfgExists ? "pass" : "fail",
|
|
14
|
+
detail: cfgExists ? cfgPath : `Not found at ${cfgPath}. Run: npx mcp-google-ads-install`
|
|
15
|
+
});
|
|
16
|
+
let cfg = null;
|
|
17
|
+
let cfgParseOk = false;
|
|
18
|
+
if (cfgExists) {
|
|
19
|
+
try {
|
|
20
|
+
cfg = JSON.parse(readFileSync(cfgPath, "utf8") || "{}");
|
|
21
|
+
cfgParseOk = true;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
cfgParseOk = false;
|
|
24
|
+
}
|
|
25
|
+
checks.push({
|
|
26
|
+
name: "claude_desktop_config is valid JSON",
|
|
27
|
+
status: cfgParseOk ? "pass" : "fail",
|
|
28
|
+
detail: cfgParseOk ? "parsed ok" : `${cfgPath} is not valid JSON. Fix syntax or delete the file and re-run mcp-google-ads-install.`
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
checks.push({
|
|
32
|
+
name: "claude_desktop_config is valid JSON",
|
|
33
|
+
status: "fail",
|
|
34
|
+
detail: "config file missing \u2014 cannot check JSON validity"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const mcpServers = cfgParseOk && cfg && typeof cfg === "object" ? cfg.mcpServers ?? {} : {};
|
|
38
|
+
const registered = Boolean(mcpServers["google-ads"]);
|
|
39
|
+
checks.push({
|
|
40
|
+
name: "google-ads registered in claude_desktop_config",
|
|
41
|
+
status: registered ? "pass" : "fail",
|
|
42
|
+
detail: registered ? "entry present under mcpServers.google-ads" : "not registered. Run: npx mcp-google-ads-install"
|
|
43
|
+
});
|
|
44
|
+
const credsExist = existsSync(credsPath);
|
|
45
|
+
checks.push({
|
|
46
|
+
name: "credentials file exists",
|
|
47
|
+
status: credsExist ? "pass" : "fail",
|
|
48
|
+
detail: credsExist ? credsPath : `Not found at ${credsPath}. Run: npx mcp-google-ads-auth`
|
|
49
|
+
});
|
|
50
|
+
let creds = {};
|
|
51
|
+
if (credsExist) {
|
|
52
|
+
try {
|
|
53
|
+
creds = JSON.parse(readFileSync(credsPath, "utf8"));
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.length > 0;
|
|
58
|
+
const hasCustomerId = typeof creds.customer_id === "string" && creds.customer_id.length > 0;
|
|
59
|
+
checks.push({
|
|
60
|
+
name: "credentials file has refresh_token and customer_id",
|
|
61
|
+
status: hasRefresh && hasCustomerId ? "pass" : credsExist ? "fail" : "warn",
|
|
62
|
+
detail: credsExist ? hasRefresh && hasCustomerId ? "both fields present" : "missing required fields. Re-run: npx mcp-google-ads-auth" : "credentials file missing \u2014 cannot check"
|
|
63
|
+
});
|
|
64
|
+
const customerId = typeof creds.customer_id === "string" ? creds.customer_id : "";
|
|
65
|
+
const mccId = typeof creds.mcc_customer_id === "string" ? creds.mcc_customer_id : "";
|
|
66
|
+
const isMccTerminal = customerId.length > 0 && customerId === mccId;
|
|
67
|
+
checks.push({
|
|
68
|
+
name: "customer_id is a leaf account, not an MCC",
|
|
69
|
+
status: !hasCustomerId ? "warn" : isMccTerminal ? "warn" : "pass",
|
|
70
|
+
detail: !hasCustomerId ? "customer_id missing \u2014 cannot check" : isMccTerminal ? `customer_id (${customerId}) equals mcc_customer_id \u2014 this is a Manager account. Most tools need a leaf. Re-run: npx mcp-google-ads-auth and pick a client under the MCC.` : `leaf account (customer_id=${customerId}${mccId ? `, under MCC ${mccId}` : ", direct access"})`
|
|
71
|
+
});
|
|
72
|
+
return checks;
|
|
73
|
+
}
|
|
74
|
+
function checkNodeVersion() {
|
|
75
|
+
const match = process.version.match(/^v(\d+)\./);
|
|
76
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
77
|
+
return {
|
|
78
|
+
name: "node version >= 18",
|
|
79
|
+
status: major >= 18 ? "pass" : "fail",
|
|
80
|
+
detail: major >= 18 ? process.version : `${process.version} is too old. Install Node 18+ from https://nodejs.org/`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function renderChecks(checks) {
|
|
84
|
+
const icon = (s) => s === "pass" ? "\u2713" : s === "warn" ? "\u26A0" : "\u2717";
|
|
85
|
+
const lines = ["", "mcp-google-ads \u2014 diagnostic", ""];
|
|
86
|
+
for (const c of checks) {
|
|
87
|
+
lines.push(` ${icon(c.status)} ${c.name}`);
|
|
88
|
+
lines.push(` ${c.detail}`);
|
|
89
|
+
}
|
|
90
|
+
const failed = checks.filter((c) => c.status === "fail").length;
|
|
91
|
+
const warned = checks.filter((c) => c.status === "warn").length;
|
|
92
|
+
lines.push("");
|
|
93
|
+
if (failed === 0 && warned === 0) {
|
|
94
|
+
lines.push("All checks passed. If Claude Desktop still shows the MCP as disconnected,");
|
|
95
|
+
lines.push("fully quit Claude Desktop (Cmd+Q) and reopen it.");
|
|
96
|
+
} else {
|
|
97
|
+
lines.push(`${failed} failure${failed === 1 ? "" : "s"}, ${warned} warning${warned === 1 ? "" : "s"}. See details above.`);
|
|
98
|
+
}
|
|
99
|
+
lines.push("");
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
function parseArgs(argv) {
|
|
103
|
+
const out = { help: false };
|
|
104
|
+
for (let i = 0; i < argv.length; i++) {
|
|
105
|
+
const a = argv[i];
|
|
106
|
+
if (a === "--help" || a === "-h") out.help = true;
|
|
107
|
+
else if (a === "--config" && argv[i + 1]) out.configPath = argv[++i];
|
|
108
|
+
else if (a === "--credentials" && argv[i + 1]) out.credentialsPath = argv[++i];
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
async function run(argv = process.argv.slice(2)) {
|
|
113
|
+
const args = parseArgs(argv);
|
|
114
|
+
if (args.help) {
|
|
115
|
+
process.stderr.write("mcp-google-ads-doctor \u2014 diagnose Claude Desktop + Google Ads MCP setup\n");
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
const checks = await runDoctor(args);
|
|
119
|
+
process.stdout.write(renderChecks(checks));
|
|
120
|
+
return checks.some((c) => c.status === "fail") ? 1 : 0;
|
|
121
|
+
}
|
|
122
|
+
import { fileURLToPath } from "url";
|
|
123
|
+
import { realpathSync } from "fs";
|
|
124
|
+
function isMainModule() {
|
|
125
|
+
if (!process.argv[1]) return false;
|
|
126
|
+
try {
|
|
127
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (isMainModule()) {
|
|
133
|
+
run().then((code) => process.exit(code)).catch((err) => {
|
|
134
|
+
process.stderr.write(`
|
|
135
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
136
|
+
`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
run,
|
|
142
|
+
runDoctor
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=doctor-cli.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface InstallOptions {
|
|
3
|
+
configPath?: string;
|
|
4
|
+
customerId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveDefaultConfigPath(): string;
|
|
7
|
+
export declare function installIntoConfig(opts?: InstallOptions): {
|
|
8
|
+
path: string;
|
|
9
|
+
existed: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function run(argv?: string[]): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir, platform } from "os";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
function resolveDefaultConfigPath() {
|
|
6
|
+
const home = homedir();
|
|
7
|
+
switch (platform()) {
|
|
8
|
+
case "darwin":
|
|
9
|
+
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
10
|
+
case "win32":
|
|
11
|
+
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
12
|
+
default:
|
|
13
|
+
return join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function installIntoConfig(opts = {}) {
|
|
17
|
+
const configPath = opts.configPath ?? resolveDefaultConfigPath();
|
|
18
|
+
const existed = existsSync(configPath);
|
|
19
|
+
let config = {};
|
|
20
|
+
if (existed) {
|
|
21
|
+
const raw = readFileSync(configPath, "utf8");
|
|
22
|
+
if (raw.trim().length > 0) {
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
26
|
+
config = parsed;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Claude Desktop config at ${configPath} is not valid JSON. Refusing to overwrite. Open the file, fix the syntax (or delete it to start fresh), and re-run.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
const normalizedCustomerId = opts.customerId?.replace(/-/g, "");
|
|
38
|
+
const entry = {
|
|
39
|
+
command: "npx",
|
|
40
|
+
args: ["-y", "mcp-google-ads@latest"]
|
|
41
|
+
};
|
|
42
|
+
if (normalizedCustomerId) {
|
|
43
|
+
entry.env = { GOOGLE_ADS_CUSTOMER_ID: normalizedCustomerId };
|
|
44
|
+
}
|
|
45
|
+
const nextConfig = {
|
|
46
|
+
...config,
|
|
47
|
+
mcpServers: {
|
|
48
|
+
...config.mcpServers ?? {},
|
|
49
|
+
"google-ads": entry
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
writeFileSync(configPath, JSON.stringify(nextConfig, null, 2) + "\n", { mode: 420 });
|
|
53
|
+
return { path: configPath, existed };
|
|
54
|
+
}
|
|
55
|
+
function parseArgs(argv) {
|
|
56
|
+
const out = { help: false };
|
|
57
|
+
for (let i = 0; i < argv.length; i++) {
|
|
58
|
+
const a = argv[i];
|
|
59
|
+
if (a === "--help" || a === "-h") out.help = true;
|
|
60
|
+
else if (a === "--customer-id" && argv[i + 1]) out.customerId = argv[++i];
|
|
61
|
+
else if (a === "--config" && argv[i + 1]) out.configPath = argv[++i];
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function printHelp() {
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
[
|
|
68
|
+
"mcp-google-ads-install \u2014 add mcp-google-ads to Claude Desktop config",
|
|
69
|
+
"",
|
|
70
|
+
"Usage:",
|
|
71
|
+
" npx mcp-google-ads-install",
|
|
72
|
+
" npx mcp-google-ads-install --customer-id 745-851-7309",
|
|
73
|
+
"",
|
|
74
|
+
"Options:",
|
|
75
|
+
" --customer-id <id> Pin the customer ID in the config (skips auth picker later)",
|
|
76
|
+
" --config <path> Override the default Claude Desktop config path",
|
|
77
|
+
" -h, --help Show this help",
|
|
78
|
+
"",
|
|
79
|
+
`Default config path: ${resolveDefaultConfigPath()}`,
|
|
80
|
+
""
|
|
81
|
+
].join("\n")
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
async function run(argv = process.argv.slice(2)) {
|
|
85
|
+
const args = parseArgs(argv);
|
|
86
|
+
if (args.help) {
|
|
87
|
+
printHelp();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const result = installIntoConfig(args);
|
|
91
|
+
process.stdout.write(
|
|
92
|
+
[
|
|
93
|
+
"",
|
|
94
|
+
`\u2713 ${result.existed ? "Updated" : "Created"}: ${result.path}`,
|
|
95
|
+
"",
|
|
96
|
+
"Next steps:",
|
|
97
|
+
" 1. Fully quit Claude Desktop (Cmd+Q on macOS, not just close the window).",
|
|
98
|
+
" 2. Reopen Claude Desktop.",
|
|
99
|
+
" 3. If you haven't authenticated yet, run: npx mcp-google-ads-auth",
|
|
100
|
+
""
|
|
101
|
+
].join("\n")
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
import { fileURLToPath } from "url";
|
|
105
|
+
import { realpathSync } from "fs";
|
|
106
|
+
function isMainModule() {
|
|
107
|
+
if (!process.argv[1]) return false;
|
|
108
|
+
try {
|
|
109
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (isMainModule()) {
|
|
115
|
+
run().catch((err) => {
|
|
116
|
+
process.stderr.write(`
|
|
117
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
118
|
+
`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
installIntoConfig,
|
|
124
|
+
resolveDefaultConfigPath,
|
|
125
|
+
run
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=install-cli.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-ads",
|
|
3
3
|
"mcpName": "io.github.mharnett/google-ads",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
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",
|
|
9
|
-
"mcp-google-ads-auth": "dist/auth-cli.js"
|
|
9
|
+
"mcp-google-ads-auth": "dist/auth-cli.js",
|
|
10
|
+
"mcp-google-ads-install": "dist/install-cli.js",
|
|
11
|
+
"mcp-google-ads-doctor": "dist/doctor-cli.js"
|
|
10
12
|
},
|
|
11
13
|
"types": "dist/index.d.ts",
|
|
12
14
|
"type": "module",
|
|
@@ -14,6 +16,18 @@
|
|
|
14
16
|
".": {
|
|
15
17
|
"import": "./dist/index.js",
|
|
16
18
|
"types": "./dist/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./auth-cli": {
|
|
21
|
+
"import": "./dist/auth-cli.js",
|
|
22
|
+
"types": "./dist/auth-cli.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./install-cli": {
|
|
25
|
+
"import": "./dist/install-cli.js",
|
|
26
|
+
"types": "./dist/install-cli.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./doctor-cli": {
|
|
29
|
+
"import": "./dist/doctor-cli.js",
|
|
30
|
+
"types": "./dist/doctor-cli.d.ts"
|
|
17
31
|
}
|
|
18
32
|
},
|
|
19
33
|
"files": [
|