mcp-google-ads 1.0.6 → 1.0.7
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 +1 -1
- package/dist/index.js +10 -2
- package/dist/resilience.js +46 -18
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sha":"
|
|
1
|
+
{"sha":"3490598","builtAt":"2026-04-09T21:18:29.192Z"}
|
package/dist/index.js
CHANGED
|
@@ -1151,8 +1151,8 @@ class GoogleAdsManager {
|
|
|
1151
1151
|
const config = loadConfig();
|
|
1152
1152
|
const adsManager = new GoogleAdsManager(config);
|
|
1153
1153
|
const server = new Server({
|
|
1154
|
-
name:
|
|
1155
|
-
version:
|
|
1154
|
+
name: __cliPkg.name,
|
|
1155
|
+
version: __cliPkg.version,
|
|
1156
1156
|
}, {
|
|
1157
1157
|
capabilities: {
|
|
1158
1158
|
tools: {},
|
|
@@ -1806,4 +1806,12 @@ async function main() {
|
|
|
1806
1806
|
await server.connect(transport);
|
|
1807
1807
|
logger.info("MCP Google Ads server running");
|
|
1808
1808
|
}
|
|
1809
|
+
process.on("SIGTERM", () => {
|
|
1810
|
+
console.error("[shutdown] SIGTERM received, exiting");
|
|
1811
|
+
process.exit(0);
|
|
1812
|
+
});
|
|
1813
|
+
process.on("SIGINT", () => {
|
|
1814
|
+
console.error("[shutdown] SIGINT received, exiting");
|
|
1815
|
+
process.exit(0);
|
|
1816
|
+
});
|
|
1809
1817
|
main().catch((err) => logger.error({ error: err.message, stack: err.stack }, "Fatal startup error"));
|
package/dist/resilience.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { retry, circuitBreaker, wrap,
|
|
1
|
+
import { retry, circuitBreaker, wrap, handleWhen, timeout, TimeoutStrategy, ExponentialBackoff, ConsecutiveBreaker, } from "cockatiel";
|
|
2
2
|
import pino from "pino";
|
|
3
3
|
// ============================================
|
|
4
4
|
// LOGGER
|
|
@@ -21,27 +21,39 @@ export const logger = pino({
|
|
|
21
21
|
// ============================================
|
|
22
22
|
const MAX_RESPONSE_SIZE = 200_000; // 200KB
|
|
23
23
|
export function safeResponse(data, context) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
let current = data;
|
|
25
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
26
|
+
const jsonStr = JSON.stringify(current);
|
|
27
|
+
const sizeBytes = Buffer.byteLength(jsonStr, "utf-8");
|
|
28
|
+
if (sizeBytes <= MAX_RESPONSE_SIZE)
|
|
29
|
+
return current;
|
|
30
|
+
logger.warn({ sizeBytes, maxSize: MAX_RESPONSE_SIZE, context, pass }, "Response exceeds size limit, truncating");
|
|
31
|
+
if (Array.isArray(current)) {
|
|
32
|
+
current = current.slice(0, Math.max(1, Math.floor(current.length * 0.5)));
|
|
33
|
+
continue;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for (const key of ["items", "results", "data", "rows"]) {
|
|
37
|
-
if (Array.isArray(obj[key])) {
|
|
35
|
+
if (typeof current === "object" && current !== null) {
|
|
36
|
+
const obj = current;
|
|
37
|
+
let truncated = false;
|
|
38
|
+
for (const key of ["items", "results", "data", "rows", "tags", "triggers", "variables"]) {
|
|
39
|
+
if (Array.isArray(obj[key]) && obj[key].length > 1) {
|
|
38
40
|
obj[key] = obj[key].slice(0, Math.max(1, Math.floor(obj[key].length * 0.5)));
|
|
39
|
-
|
|
41
|
+
if ("count" in obj)
|
|
42
|
+
obj.count = obj[key].length;
|
|
43
|
+
if ("row_count" in obj)
|
|
44
|
+
obj.row_count = obj[key].length;
|
|
45
|
+
obj.truncated = true;
|
|
46
|
+
truncated = true;
|
|
47
|
+
break;
|
|
40
48
|
}
|
|
41
49
|
}
|
|
50
|
+
if (truncated)
|
|
51
|
+
continue;
|
|
42
52
|
}
|
|
53
|
+
// Can't truncate further (not an array/object with known keys)
|
|
54
|
+
break;
|
|
43
55
|
}
|
|
44
|
-
return
|
|
56
|
+
return current;
|
|
45
57
|
}
|
|
46
58
|
// ============================================
|
|
47
59
|
// RETRY + CIRCUIT BREAKER + TIMEOUT
|
|
@@ -50,12 +62,28 @@ const backoff = new ExponentialBackoff({
|
|
|
50
62
|
initialDelay: 100,
|
|
51
63
|
maxDelay: 5_000,
|
|
52
64
|
});
|
|
65
|
+
const isTransient = handleWhen((err) => {
|
|
66
|
+
const msg = (err?.message || "").toLowerCase();
|
|
67
|
+
const code = err?.code || err?.status;
|
|
68
|
+
// Don't retry auth errors
|
|
69
|
+
if (code === 401 || code === 403 || code === 7 || code === 16)
|
|
70
|
+
return false;
|
|
71
|
+
if (msg.includes("unauthenticated") || msg.includes("permission_denied") || msg.includes("invalid_grant"))
|
|
72
|
+
return false;
|
|
73
|
+
// Don't retry client errors (except rate limits)
|
|
74
|
+
if (code === 429 || msg.includes("rate"))
|
|
75
|
+
return true;
|
|
76
|
+
if (code >= 400 && code < 500)
|
|
77
|
+
return false;
|
|
78
|
+
// Retry everything else (5xx, timeouts, network errors)
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
53
81
|
// Individual policies
|
|
54
|
-
const retryPolicy = retry(
|
|
82
|
+
const retryPolicy = retry(isTransient, {
|
|
55
83
|
maxAttempts: 3,
|
|
56
84
|
backoff,
|
|
57
85
|
});
|
|
58
|
-
const circuitBreakerPolicy = circuitBreaker(
|
|
86
|
+
const circuitBreakerPolicy = circuitBreaker(isTransient, {
|
|
59
87
|
halfOpenAfter: 60_000, // 60s to attempt recovery
|
|
60
88
|
breaker: new ConsecutiveBreaker(5), // Open after 5 consecutive failures
|
|
61
89
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-ads",
|
|
3
3
|
"mcpName": "io.github.mharnett/google-ads",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"description": "MCP server for Google Ads API with MCC support, 35 tools for campaign management, reporting, and optimization. Safe by default — all changes created PAUSED.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|