mobile-growth-mcp 2.3.8 → 2.3.9
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/index.js +103 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -63,43 +63,50 @@ async function jsonRpcRequest(apiKey2, method, params) {
|
|
|
63
63
|
throw err;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
async function jsonRpcRequestWithRetry(apiKey2, method, params, maxAttempts = 2) {
|
|
67
|
+
let lastError;
|
|
68
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
return await jsonRpcRequest(apiKey2, method, params);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
lastError = err;
|
|
73
|
+
const isRetryable = lastError.name === "AbortError" || lastError.name === "TimeoutError" || lastError.message?.includes("fetch failed");
|
|
74
|
+
if (!isRetryable || attempt === maxAttempts) break;
|
|
75
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw lastError ?? new Error(`${method} failed after ${maxAttempts} attempts`);
|
|
79
|
+
}
|
|
66
80
|
async function fetchRemoteTools(apiKey2) {
|
|
67
|
-
const resp = await
|
|
81
|
+
const resp = await jsonRpcRequestWithRetry(apiKey2, "tools/list");
|
|
68
82
|
if (resp.error) {
|
|
69
83
|
throw new Error(`tools/list error: ${resp.error.message}`);
|
|
70
84
|
}
|
|
71
85
|
return resp.result?.tools ?? [];
|
|
72
86
|
}
|
|
73
87
|
async function callRemoteTool(apiKey2, name, args) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
arguments: args
|
|
81
|
-
});
|
|
82
|
-
if (resp.error) {
|
|
83
|
-
return {
|
|
84
|
-
content: [{ type: "text", text: `Remote error: ${resp.error.message}` }],
|
|
85
|
-
isError: true
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
+
try {
|
|
89
|
+
const resp = await jsonRpcRequestWithRetry(apiKey2, "tools/call", {
|
|
90
|
+
name,
|
|
91
|
+
arguments: args
|
|
92
|
+
});
|
|
93
|
+
if (resp.error) {
|
|
88
94
|
return {
|
|
89
|
-
content:
|
|
90
|
-
isError:
|
|
95
|
+
content: [{ type: "text", text: `Remote error: ${resp.error.message}` }],
|
|
96
|
+
isError: true
|
|
91
97
|
};
|
|
92
|
-
} catch (err) {
|
|
93
|
-
lastError = err;
|
|
94
|
-
const isRetryable = lastError.name === "AbortError" || lastError.name === "TimeoutError" || lastError.message?.includes("fetch failed");
|
|
95
|
-
if (!isRetryable || attempt === maxAttempts) break;
|
|
96
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
97
98
|
}
|
|
99
|
+
return {
|
|
100
|
+
content: resp.result?.content ?? [{ type: "text", text: "No content returned" }],
|
|
101
|
+
isError: resp.result?.isError
|
|
102
|
+
};
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const lastError = err;
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: `Remote call failed after retry: ${lastError.message ?? "unknown error"}` }],
|
|
107
|
+
isError: true
|
|
108
|
+
};
|
|
98
109
|
}
|
|
99
|
-
return {
|
|
100
|
-
content: [{ type: "text", text: `Remote call failed after retry: ${lastError?.message ?? "unknown error"}` }],
|
|
101
|
-
isError: true
|
|
102
|
-
};
|
|
103
110
|
}
|
|
104
111
|
function jsonSchemaToZodShape(inputSchema) {
|
|
105
112
|
const properties = inputSchema.properties ?? {};
|
|
@@ -146,14 +153,14 @@ function registerFetchedTools(server2, apiKey2, tools) {
|
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
async function fetchRemotePrompts(apiKey2) {
|
|
149
|
-
const resp = await
|
|
156
|
+
const resp = await jsonRpcRequestWithRetry(apiKey2, "prompts/list");
|
|
150
157
|
if (resp.error) {
|
|
151
158
|
throw new Error(`prompts/list error: ${resp.error.message}`);
|
|
152
159
|
}
|
|
153
160
|
return resp.result?.prompts ?? [];
|
|
154
161
|
}
|
|
155
162
|
async function getRemotePrompt(apiKey2, name, args) {
|
|
156
|
-
const resp = await
|
|
163
|
+
const resp = await jsonRpcRequestWithRetry(apiKey2, "prompts/get", {
|
|
157
164
|
name,
|
|
158
165
|
arguments: args
|
|
159
166
|
});
|
|
@@ -1556,13 +1563,16 @@ Sources: goog-pdf-018, ab-pt-008, goog-pdf-019, ab-pt-007
|
|
|
1556
1563
|
import { z as z8 } from "zod";
|
|
1557
1564
|
import { readFile } from "fs/promises";
|
|
1558
1565
|
import { basename, resolve } from "path";
|
|
1566
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
1559
1567
|
async function fetchAsBase64(url) {
|
|
1560
1568
|
if (url.startsWith("data:")) {
|
|
1561
1569
|
const commaIdx = url.indexOf(",");
|
|
1562
1570
|
if (commaIdx === -1) throw new Error("Invalid data URI");
|
|
1563
1571
|
return url.slice(commaIdx + 1);
|
|
1564
1572
|
}
|
|
1565
|
-
const res = await fetch(url
|
|
1573
|
+
const res = await fetch(url, {
|
|
1574
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1575
|
+
});
|
|
1566
1576
|
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
|
1567
1577
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
1568
1578
|
return buf.toString("base64");
|
|
@@ -1625,38 +1635,68 @@ Would link to campaign ${campaign_id} as ${effectiveFieldType}`;
|
|
|
1625
1635
|
}
|
|
1626
1636
|
return { content: [{ type: "text", text: text2 }] };
|
|
1627
1637
|
}
|
|
1638
|
+
const assetNames = images.map(
|
|
1639
|
+
(img) => img.name ?? basename(img.source).replace(/\?.*$/, "") ?? "unnamed"
|
|
1640
|
+
);
|
|
1641
|
+
const fetchResults = await Promise.allSettled(
|
|
1642
|
+
images.map((img) => {
|
|
1643
|
+
const isUrl = img.source.startsWith("http://") || img.source.startsWith("https://") || img.source.startsWith("data:");
|
|
1644
|
+
return isUrl ? fetchAsBase64(img.source) : readFileAsBase64(img.source);
|
|
1645
|
+
})
|
|
1646
|
+
);
|
|
1628
1647
|
const assetOps = [];
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
imageAsset: { data }
|
|
1648
|
+
const opOriginIndex = [];
|
|
1649
|
+
const fetchFailures = [];
|
|
1650
|
+
fetchResults.forEach((r, i) => {
|
|
1651
|
+
if (r.status === "fulfilled") {
|
|
1652
|
+
assetOps.push({
|
|
1653
|
+
assetOperation: {
|
|
1654
|
+
create: {
|
|
1655
|
+
name: assetNames[i],
|
|
1656
|
+
type: "IMAGE",
|
|
1657
|
+
imageAsset: { data: r.value }
|
|
1658
|
+
}
|
|
1641
1659
|
}
|
|
1642
|
-
}
|
|
1660
|
+
});
|
|
1661
|
+
opOriginIndex.push(i);
|
|
1662
|
+
} else {
|
|
1663
|
+
fetchFailures.push({
|
|
1664
|
+
index: i,
|
|
1665
|
+
error: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
const createdResourceNames = new Array(images.length).fill("");
|
|
1670
|
+
let successCount = 0;
|
|
1671
|
+
if (assetOps.length > 0) {
|
|
1672
|
+
const assetResult = await googleAdsMutate(normalizedId, assetOps);
|
|
1673
|
+
const responses = assetResult.mutateOperationResponses;
|
|
1674
|
+
if (responses.length !== assetOps.length) {
|
|
1675
|
+
throw new Error(
|
|
1676
|
+
`Google Ads response length mismatch: sent ${assetOps.length} ops, got ${responses.length} responses`
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
responses.forEach((r, i) => {
|
|
1680
|
+
const originalIdx = opOriginIndex[i];
|
|
1681
|
+
const rn = r.assetResult?.resourceName ?? "";
|
|
1682
|
+
createdResourceNames[originalIdx] = rn;
|
|
1683
|
+
if (rn) successCount++;
|
|
1643
1684
|
});
|
|
1644
1685
|
}
|
|
1645
|
-
const assetResult = await googleAdsMutate(normalizedId, assetOps);
|
|
1646
|
-
const createdResourceNames = assetResult.mutateOperationResponses.map(
|
|
1647
|
-
(r) => r.assetResult?.resourceName ?? ""
|
|
1648
|
-
);
|
|
1649
|
-
const successCount = createdResourceNames.filter(Boolean).length;
|
|
1650
1686
|
let text = `**Uploaded ${successCount}/${images.length} image assets**
|
|
1651
1687
|
|
|
1652
1688
|
`;
|
|
1653
1689
|
for (let i = 0; i < images.length; i++) {
|
|
1654
1690
|
const rn = createdResourceNames[i];
|
|
1691
|
+
const failure = fetchFailures.find((f) => f.index === i);
|
|
1655
1692
|
if (rn) {
|
|
1656
1693
|
text += `\u2713 ${assetNames[i]} \u2192 ${rn}
|
|
1694
|
+
`;
|
|
1695
|
+
} else if (failure) {
|
|
1696
|
+
text += `\u2717 ${assetNames[i]} \u2014 fetch failed: ${failure.error}
|
|
1657
1697
|
`;
|
|
1658
1698
|
} else {
|
|
1659
|
-
text += `\u2717 ${assetNames[i]} \u2014 failed
|
|
1699
|
+
text += `\u2717 ${assetNames[i]} \u2014 upload failed
|
|
1660
1700
|
`;
|
|
1661
1701
|
}
|
|
1662
1702
|
}
|
|
@@ -1715,7 +1755,7 @@ Assets exist in the account and can be linked manually.`;
|
|
|
1715
1755
|
}
|
|
1716
1756
|
|
|
1717
1757
|
// src/tools/connection-status.ts
|
|
1718
|
-
var SERVER_VERSION = true ? "2.3.
|
|
1758
|
+
var SERVER_VERSION = true ? "2.3.9" : "dev";
|
|
1719
1759
|
function registerConnectionStatus(server2, status2) {
|
|
1720
1760
|
server2.tool(
|
|
1721
1761
|
"connection_status",
|
|
@@ -2336,8 +2376,10 @@ function saveToEnv(vars) {
|
|
|
2336
2376
|
`);
|
|
2337
2377
|
}
|
|
2338
2378
|
}
|
|
2379
|
+
var OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2339
2380
|
async function waitForOAuthCallback(clientId, clientSecret) {
|
|
2340
2381
|
return new Promise((resolve3, reject) => {
|
|
2382
|
+
let timer;
|
|
2341
2383
|
const server2 = createServer(
|
|
2342
2384
|
async (req, res) => {
|
|
2343
2385
|
const url = new URL(req.url ?? "/", `http://localhost:${OAUTH_PORT}`);
|
|
@@ -2404,8 +2446,20 @@ async function waitForOAuthCallback(clientId, clientSecret) {
|
|
|
2404
2446
|
}
|
|
2405
2447
|
);
|
|
2406
2448
|
server2.listen(OAUTH_PORT, () => {
|
|
2449
|
+
timer = setTimeout(() => {
|
|
2450
|
+
server2.close();
|
|
2451
|
+
reject(
|
|
2452
|
+
new Error(
|
|
2453
|
+
`OAuth authorization timed out after ${OAUTH_CALLBACK_TIMEOUT_MS / 1e3}s. Re-run the auth command and complete the consent screen in the browser.`
|
|
2454
|
+
)
|
|
2455
|
+
);
|
|
2456
|
+
}, OAUTH_CALLBACK_TIMEOUT_MS);
|
|
2457
|
+
});
|
|
2458
|
+
server2.on("close", () => {
|
|
2459
|
+
if (timer) clearTimeout(timer);
|
|
2407
2460
|
});
|
|
2408
2461
|
server2.on("error", (err) => {
|
|
2462
|
+
if (timer) clearTimeout(timer);
|
|
2409
2463
|
if (err.code === "EADDRINUSE") {
|
|
2410
2464
|
reject(
|
|
2411
2465
|
new Error(
|
package/package.json
CHANGED