mcp-google-ads 1.6.0 → 1.7.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/README.md +29 -3
- package/config.example.json +3 -0
- package/dist/auth-cli.d.ts +9 -0
- package/dist/auth-cli.js +38 -15
- package/dist/build-info.json +2 -2
- package/dist/index.d.ts +523 -0
- package/dist/index.js +271 -88
- package/dist/oauthScope.d.ts +14 -0
- package/dist/oauthScope.js +25 -0
- package/dist/pkce.d.ts +9 -0
- package/dist/pkce.js +24 -0
- package/dist/resilience.js +40 -25
- package/dist/tools.js +71 -3
- package/dist/validateDemandGenAd.d.ts +2 -2
- package/dist/validateDemandGenAd.js +3 -4
- package/dist/validateRsa.d.ts +8 -0
- package/dist/validateRsa.js +15 -2
- package/dist/writeGate.js +17 -10
- package/get-refresh-token.cjs +295 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -32,13 +32,36 @@ You need:
|
|
|
32
32
|
|
|
33
33
|
#### Getting a Refresh Token
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Bring your own OAuth client (the `client_id` / `client_secret` from the step
|
|
36
|
+
above) and run the bundled helper. It runs Google's installed-app loopback flow
|
|
37
|
+
**with PKCE (S256)** and prints your refresh token. It reads nothing from your
|
|
38
|
+
home directory and needs no shared OAuth keyfile.
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
export GOOGLE_ADS_CLIENT_ID="YOUR_CLIENT_ID.apps.googleusercontent.com"
|
|
42
|
+
export GOOGLE_ADS_CLIENT_SECRET="YOUR_CLIENT_SECRET"
|
|
43
|
+
node get-refresh-token.cjs
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
Your browser opens for Google sign-in; approve as the Google account that owns
|
|
47
|
+
the Ads data. On success the helper prints one line to stdout:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
GOOGLE_ADS_REFRESH_TOKEN=1//0a...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Set that value in your environment (or `config.json`, below). The OAuth scope
|
|
54
|
+
requested is read from `config.json` (`oauth.scope`), falling back to
|
|
55
|
+
`config.example.json`, so the helper and the running server always request the
|
|
56
|
+
same scope. This MCP requests only the minimum scope it needs:
|
|
57
|
+
`https://www.googleapis.com/auth/adwords`.
|
|
58
|
+
|
|
59
|
+
> Do not run this with stdout redirected to a shared log file — the refresh
|
|
60
|
+
> token is printed to stdout by design.
|
|
61
|
+
|
|
62
|
+
Note: `GOOGLE_ADS_DEVELOPER_TOKEN` is a separate Google Ads API credential, not
|
|
63
|
+
an OAuth scope — set it independently (see Environment Variables below).
|
|
64
|
+
|
|
42
65
|
### 2. Install
|
|
43
66
|
|
|
44
67
|
```bash
|
|
@@ -66,6 +89,9 @@ Edit `config.json` with your credentials:
|
|
|
66
89
|
|
|
67
90
|
```json
|
|
68
91
|
{
|
|
92
|
+
"oauth": {
|
|
93
|
+
"scope": "https://www.googleapis.com/auth/adwords"
|
|
94
|
+
},
|
|
69
95
|
"google_ads": {
|
|
70
96
|
"developer_token": "YOUR_DEVELOPER_TOKEN",
|
|
71
97
|
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
|
package/config.example.json
CHANGED
package/dist/auth-cli.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
export declare const OAUTH_SCOPE: string;
|
|
3
|
+
export declare function buildAuthUrl(clientId: string, redirectUri: string, state: string, codeChallenge: string): string;
|
|
4
|
+
export declare function buildTokenExchangeBody(opts: {
|
|
5
|
+
code: string;
|
|
6
|
+
clientId: string;
|
|
7
|
+
clientSecret: string;
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
codeVerifier: string;
|
|
10
|
+
}): string;
|
|
2
11
|
export declare function run(argv?: string[]): Promise<void>;
|
package/dist/auth-cli.js
CHANGED
|
@@ -13,8 +13,17 @@ import {
|
|
|
13
13
|
import { classifyError, GoogleAdsAuthError } from "./errors.js";
|
|
14
14
|
import { findFreeLoopbackPort, openBrowser } from "./platform.js";
|
|
15
15
|
import { logger, withResilience } from "./resilience.js";
|
|
16
|
+
import { dirname, join } from "path";
|
|
17
|
+
import { loadOAuthScopeFromFile } from "./oauthScope.js";
|
|
18
|
+
import {
|
|
19
|
+
generateCodeVerifier,
|
|
20
|
+
computeCodeChallenge,
|
|
21
|
+
buildLoopbackRedirectUri
|
|
22
|
+
} from "./pkce.js";
|
|
16
23
|
const prompts = promptsImport.default ?? promptsImport;
|
|
17
|
-
const OAUTH_SCOPE =
|
|
24
|
+
const OAUTH_SCOPE = loadOAuthScopeFromFile(
|
|
25
|
+
join(dirname(fileURLToPath(import.meta.url)), "..", "config.json")
|
|
26
|
+
);
|
|
18
27
|
const OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
19
28
|
const OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
20
29
|
function parseArgs(argv) {
|
|
@@ -46,7 +55,7 @@ function printHelp() {
|
|
|
46
55
|
].join("\n")
|
|
47
56
|
);
|
|
48
57
|
}
|
|
49
|
-
function buildAuthUrl(clientId, redirectUri, state) {
|
|
58
|
+
function buildAuthUrl(clientId, redirectUri, state, codeChallenge) {
|
|
50
59
|
const params = new URLSearchParams({
|
|
51
60
|
client_id: clientId,
|
|
52
61
|
redirect_uri: redirectUri,
|
|
@@ -54,7 +63,10 @@ function buildAuthUrl(clientId, redirectUri, state) {
|
|
|
54
63
|
scope: OAUTH_SCOPE,
|
|
55
64
|
access_type: "offline",
|
|
56
65
|
prompt: "consent",
|
|
57
|
-
state
|
|
66
|
+
state,
|
|
67
|
+
code_challenge: codeChallenge,
|
|
68
|
+
// PKCE (RFC 7636)
|
|
69
|
+
code_challenge_method: "S256"
|
|
58
70
|
});
|
|
59
71
|
return `${OAUTH_AUTH_URL}?${params.toString()}`;
|
|
60
72
|
}
|
|
@@ -161,19 +173,25 @@ function renderAuthCompletePage(title, body) {
|
|
|
161
173
|
function escapeHtml(s) {
|
|
162
174
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
163
175
|
}
|
|
164
|
-
|
|
176
|
+
function buildTokenExchangeBody(opts) {
|
|
177
|
+
return new URLSearchParams({
|
|
178
|
+
code: opts.code,
|
|
179
|
+
client_id: opts.clientId,
|
|
180
|
+
client_secret: opts.clientSecret,
|
|
181
|
+
// confidential Desktop client — PKCE is additive
|
|
182
|
+
redirect_uri: opts.redirectUri,
|
|
183
|
+
grant_type: "authorization_code",
|
|
184
|
+
code_verifier: opts.codeVerifier
|
|
185
|
+
// PKCE proof — sent on exchange
|
|
186
|
+
}).toString();
|
|
187
|
+
}
|
|
188
|
+
async function exchangeCodeForTokens(code, clientId, clientSecret, redirectUri, codeVerifier) {
|
|
165
189
|
return withResilience(async () => {
|
|
166
|
-
const body =
|
|
167
|
-
code,
|
|
168
|
-
client_id: clientId,
|
|
169
|
-
client_secret: clientSecret,
|
|
170
|
-
redirect_uri: redirectUri,
|
|
171
|
-
grant_type: "authorization_code"
|
|
172
|
-
});
|
|
190
|
+
const body = buildTokenExchangeBody({ code, clientId, clientSecret, redirectUri, codeVerifier });
|
|
173
191
|
const res = await fetch(OAUTH_TOKEN_URL, {
|
|
174
192
|
method: "POST",
|
|
175
193
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
176
|
-
body
|
|
194
|
+
body
|
|
177
195
|
});
|
|
178
196
|
const json = await res.json();
|
|
179
197
|
if (!res.ok || json.error) {
|
|
@@ -348,13 +366,15 @@ async function run(argv = process.argv.slice(2)) {
|
|
|
348
366
|
process.exit(2);
|
|
349
367
|
}
|
|
350
368
|
const port = await findFreeLoopbackPort();
|
|
351
|
-
const redirectUri =
|
|
369
|
+
const redirectUri = buildLoopbackRedirectUri(port);
|
|
352
370
|
const state = randomState();
|
|
353
|
-
const
|
|
371
|
+
const codeVerifier = generateCodeVerifier();
|
|
372
|
+
const codeChallenge = computeCodeChallenge(codeVerifier);
|
|
373
|
+
const authUrl = buildAuthUrl(clientId, redirectUri, state, codeChallenge);
|
|
354
374
|
process.stderr.write("\n=== mcp-google-ads authentication ===\n");
|
|
355
375
|
const { code } = await waitForAuthorizationCode(port, state, authUrl);
|
|
356
376
|
process.stderr.write("Authorization code received. Exchanging for tokens...\n");
|
|
357
|
-
const tokens = await exchangeCodeForTokens(code, clientId, clientSecret, redirectUri);
|
|
377
|
+
const tokens = await exchangeCodeForTokens(code, clientId, clientSecret, redirectUri, codeVerifier);
|
|
358
378
|
if (!tokens.refresh_token) {
|
|
359
379
|
throw new GoogleAdsAuthError(
|
|
360
380
|
"Google did not return a refresh token. This can happen if you previously granted consent to this app \u2014 revoke access at https://myaccount.google.com/permissions and try again."
|
|
@@ -419,6 +439,9 @@ if (isMain) {
|
|
|
419
439
|
});
|
|
420
440
|
}
|
|
421
441
|
export {
|
|
442
|
+
OAUTH_SCOPE,
|
|
443
|
+
buildAuthUrl,
|
|
444
|
+
buildTokenExchangeBody,
|
|
422
445
|
run
|
|
423
446
|
};
|
|
424
447
|
//# sourceMappingURL=auth-cli.js.map
|
package/dist/build-info.json
CHANGED