@aligulzar729/google-ads-mcp 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +15 -1
  2. package/dist/index.js +64 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,11 +34,13 @@ AI: Created "Summer Sale" (PAUSED). It won't serve until you enable it.
34
34
  - [Configuration](#configuration) (and [all environment variables](#all-environment-variables))
35
35
  - [Hosted, multi-user](#hosted-multi-user) (and [LibreChat on the same host](#librechat-on-the-same-host-no-public-url))
36
36
  - [Tools](#tools)
37
+ - [Coverage](#coverage)
37
38
  - [Big reports and pagination](#big-reports-and-pagination)
38
39
  - [Scheduled and unattended use](#scheduled-and-unattended-use)
39
40
  - [Troubleshooting](#troubleshooting)
40
41
  - [Notes](#notes)
41
42
  - [Acknowledgments](#acknowledgments)
43
+ - [Disclaimer](#disclaimer)
42
44
  - [License](#license)
43
45
  - [Contributing](#contributing)
44
46
 
@@ -441,6 +443,14 @@ Seven tools, each takes an `action`:
441
443
 
442
444
  A few things worth knowing: ROAS is computed as conversion value over cost. Average Position is gone (Google removed it), so use `impression_share`. Performance Max has no ad-level reporting, so use `gaql` on its asset groups.
443
445
 
446
+ ## Coverage
447
+
448
+ | | |
449
+ |---|---|
450
+ | Google Ads API version | v24.1 (via [`google-ads-api`](https://github.com/Opteo/google-ads-api), Opteo) |
451
+ | Coverage model | Curated actions for the common workflows, plus raw GAQL over every non-sensitive report resource. Not a 1:1 mapping of the ~100 API services. |
452
+ | Write safety | Preview-first, yes/ok confirmation, PAUSED creates, durable audit log (see [Money and safety](#money-and-safety)). |
453
+
444
454
  ## Big reports and pagination
445
455
 
446
456
  Reports are bounded per response and paged without hitting Google again:
@@ -477,7 +487,7 @@ Reporting is read-only and retried, so it's reliable for cron and scheduled jobs
477
487
 
478
488
  ## Notes
479
489
 
480
- - The underlying client, `google-ads-api` (Opteo), is the community Node library for the advertiser API; Google doesn't ship an official one. It targets API v23. The high-level query and mutate calls used here are version-agnostic, but check method signatures if you upgrade the library.
490
+ - The underlying client, `google-ads-api` (Opteo), is the community Node library for the advertiser API; Google doesn't ship an official one. It targets API v24.1. The high-level query and mutate calls used here are version-agnostic, but check method signatures if you upgrade the library.
481
491
  - Error messages and tool text are in English.
482
492
  - See [SECURITY.md](SECURITY.md) for disclosure, residual risk, and key-compromise playbook. See [CHANGELOG.md](CHANGELOG.md) for release notes.
483
493
 
@@ -485,6 +495,10 @@ Reporting is read-only and retried, so it's reliable for cron and scheduled jobs
485
495
 
486
496
  This project is built on [`google-ads-api`](https://github.com/Opteo/google-ads-api), the community Node client for the Google Ads API maintained by the team at [Opteo](https://opteo.com). Google ships no official Node library for the advertiser API, and Opteo's package fills that gap with a well-typed, carefully maintained interface that handles the hard parts (OAuth, GAQL, mutates, and the Google Ads protobufs) so this server does not have to. A sincere thank you to the Opteo maintainers and contributors: this MCP server stands on their work.
487
497
 
498
+ ## Disclaimer
499
+
500
+ This is an unofficial Google Ads API integration. It is not affiliated with, endorsed by, or supported by Google. "Google Ads" is a trademark of Google LLC. You are responsible for your account and any spend; use it within Google's API Terms of Service.
501
+
488
502
  ## License
489
503
 
490
504
  MIT
package/dist/index.js CHANGED
@@ -4253,7 +4253,7 @@ function registerOAuthRoutes(app, broker) {
4253
4253
 
4254
4254
  // src/app.ts
4255
4255
  async function startHttp(config, deps) {
4256
- const { ads, audit, logger, store } = deps;
4256
+ const { ads, audit, logger, store, db } = deps;
4257
4257
  const pageCache = new PageCache();
4258
4258
  const oauthLimiter = new RateLimiter(60, 6e4);
4259
4259
  const mcpLimiter = new RateLimiter(120, 6e4);
@@ -4284,6 +4284,68 @@ async function startHttp(config, deps) {
4284
4284
  return remote || "unknown";
4285
4285
  };
4286
4286
  app.get("/healthz", (c) => c.json({ status: "ok", mode: "http" }));
4287
+ const SYNC_TABLES = ["oauth_clients", "oauth_sessions", "oauth_tokens", "audit"];
4288
+ const syncKey = process.env.INTERNAL_SYNC_KEY;
4289
+ const syncAuth = (c) => {
4290
+ if (!syncKey) return false;
4291
+ const auth = c.req.header("authorization") ?? "";
4292
+ const token = auth.startsWith("Bearer ") ? auth.slice(7).trim() : "";
4293
+ return token === syncKey;
4294
+ };
4295
+ app.get("/internal/sync", (c) => {
4296
+ if (!syncAuth(c)) return c.json({ error: "unauthorized" }, 401);
4297
+ if (c.req.query("rev") !== void 0) {
4298
+ const r = db.prepare(
4299
+ `SELECT
4300
+ (SELECT COUNT(*) FROM oauth_clients) AS c,
4301
+ (SELECT COUNT(*) FROM oauth_sessions) AS s,
4302
+ (SELECT COALESCE(MAX(last_used_ts), 0) FROM oauth_sessions) AS slu,
4303
+ (SELECT COUNT(*) FROM oauth_tokens) AS t,
4304
+ (SELECT COALESCE(MAX(id), 0) FROM audit) AS a`
4305
+ ).get();
4306
+ return c.json({ rev: `${r.c}:${r.s}:${r.slu}:${r.t}:${r.a}`, maxAuditId: r.a });
4307
+ }
4308
+ const auditSince = Number(c.req.query("auditSince") ?? 0) || 0;
4309
+ const maxAuditId = db.prepare("SELECT COALESCE(MAX(id), 0) AS a FROM audit").get().a;
4310
+ const tables = {
4311
+ oauth_clients: db.prepare("SELECT * FROM oauth_clients").all(),
4312
+ oauth_sessions: db.prepare("SELECT * FROM oauth_sessions").all(),
4313
+ oauth_tokens: db.prepare("SELECT * FROM oauth_tokens").all(),
4314
+ audit: db.prepare("SELECT * FROM audit WHERE id > ?").all(auditSince)
4315
+ };
4316
+ return c.json({ tables, maxAuditId });
4317
+ });
4318
+ app.post("/internal/sync", async (c) => {
4319
+ if (!syncAuth(c)) return c.json({ error: "unauthorized" }, 401);
4320
+ const body = await c.req.json().catch(() => null);
4321
+ if (!body?.tables || typeof body.tables !== "object") {
4322
+ return c.json({ error: "expected { tables: { ... } }" }, 400);
4323
+ }
4324
+ db.exec("BEGIN");
4325
+ try {
4326
+ for (const t of SYNC_TABLES) {
4327
+ db.prepare(`DELETE FROM ${t}`).run();
4328
+ const rows = body.tables[t] ?? [];
4329
+ for (const row of rows) {
4330
+ const cols = Object.keys(row);
4331
+ const placeholders = cols.map(() => "?").join(", ");
4332
+ db.prepare(`INSERT INTO ${t} (${cols.join(", ")}) VALUES (${placeholders})`).run(...cols.map((k) => row[k]));
4333
+ }
4334
+ }
4335
+ db.exec("COMMIT");
4336
+ } catch (e) {
4337
+ db.exec("ROLLBACK");
4338
+ logger.error("sync", "internal sync import failed", { error: e instanceof Error ? e.message : String(e) });
4339
+ return c.json({ error: "sync failed" }, 500);
4340
+ }
4341
+ logger.info("sync", "internal sync import complete", {
4342
+ clients: body.tables.oauth_clients?.length ?? 0,
4343
+ sessions: body.tables.oauth_sessions?.length ?? 0,
4344
+ tokens: body.tables.oauth_tokens?.length ?? 0,
4345
+ audit: body.tables.audit?.length ?? 0
4346
+ });
4347
+ return c.json({ ok: true });
4348
+ });
4287
4349
  app.use("/register", async (c, next) => {
4288
4350
  if (!oauthLimiter.allow(`reg:${clientIp(c)}`)) return c.json({ error: "rate_limit", message: "Too many registration requests. Retry later." }, 429);
4289
4351
  return next();
@@ -4469,7 +4531,7 @@ async function main() {
4469
4531
  const db = openDb(config.auditDbPath);
4470
4532
  const audit = new Audit(db, logger);
4471
4533
  const store = new OAuthStore(db, loadEncKey(config.tokenEncKey));
4472
- const running = await startHttp(config, { ads, audit, logger, store });
4534
+ const running = await startHttp(config, { ads, audit, logger, store, db });
4473
4535
  const shutdown = (sig) => {
4474
4536
  logger.info("main", `received ${sig}, shutting down`);
4475
4537
  const hard = setTimeout(() => process.exit(1), 8e3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligulzar729/google-ads-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.",
5
5
  "type": "module",
6
6
  "bin": {