mobile-growth-mcp 2.2.1 → 2.2.4
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 +207 -64
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ async function jsonRpcRequest(apiKey2, method, params) {
|
|
|
23
23
|
Accept: "application/json, text/event-stream"
|
|
24
24
|
},
|
|
25
25
|
body: JSON.stringify(body),
|
|
26
|
-
signal: AbortSignal.timeout(
|
|
26
|
+
signal: AbortSignal.timeout(3e4)
|
|
27
27
|
});
|
|
28
28
|
if (!res.ok) {
|
|
29
29
|
const text = await res.text();
|
|
@@ -39,19 +39,34 @@ async function fetchRemoteTools(apiKey2) {
|
|
|
39
39
|
return resp.result?.tools ?? [];
|
|
40
40
|
}
|
|
41
41
|
async function callRemoteTool(apiKey2, name, args) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
const maxAttempts = 2;
|
|
43
|
+
let lastError;
|
|
44
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
const resp = await jsonRpcRequest(apiKey2, "tools/call", {
|
|
47
|
+
name,
|
|
48
|
+
arguments: args
|
|
49
|
+
});
|
|
50
|
+
if (resp.error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: `Remote error: ${resp.error.message}` }],
|
|
53
|
+
isError: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
content: resp.result?.content ?? [{ type: "text", text: "No content returned" }],
|
|
58
|
+
isError: resp.result?.isError
|
|
59
|
+
};
|
|
60
|
+
} catch (err) {
|
|
61
|
+
lastError = err;
|
|
62
|
+
const isRetryable = lastError.name === "AbortError" || lastError.name === "TimeoutError" || lastError.message?.includes("fetch failed");
|
|
63
|
+
if (!isRetryable || attempt === maxAttempts) break;
|
|
64
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
67
|
return {
|
|
53
|
-
content:
|
|
54
|
-
isError:
|
|
68
|
+
content: [{ type: "text", text: `Remote call failed after retry: ${lastError?.message ?? "unknown error"}` }],
|
|
69
|
+
isError: true
|
|
55
70
|
};
|
|
56
71
|
}
|
|
57
72
|
function jsonSchemaToZodShape(inputSchema) {
|
|
@@ -222,7 +237,7 @@ async function metaApiGet(options) {
|
|
|
222
237
|
function activeFilter() {
|
|
223
238
|
return JSON.stringify([
|
|
224
239
|
{
|
|
225
|
-
field: "effective_status",
|
|
240
|
+
field: "ad.effective_status",
|
|
226
241
|
operator: "IN",
|
|
227
242
|
value: ["ACTIVE"]
|
|
228
243
|
}
|
|
@@ -958,6 +973,23 @@ async function googleAdsQuery(customerId, query) {
|
|
|
958
973
|
}
|
|
959
974
|
|
|
960
975
|
// src/google/format.ts
|
|
976
|
+
async function resolveCampaignId(customerId, value) {
|
|
977
|
+
if (/^\d+$/.test(value)) return value;
|
|
978
|
+
const query = `SELECT campaign.id, campaign.name FROM campaign WHERE campaign.name = '${value.replace(/'/g, "\\'")}' LIMIT 1`;
|
|
979
|
+
const chunks = await googleAdsQuery(customerId, query);
|
|
980
|
+
const rows = chunks.flatMap((c) => c.results ?? []);
|
|
981
|
+
if (rows.length === 0) throw new Error(`Campaign not found by name: "${value}"`);
|
|
982
|
+
return rows[0].campaign.id;
|
|
983
|
+
}
|
|
984
|
+
async function resolveAdGroupId(customerId, value, campaignId) {
|
|
985
|
+
if (/^\d+$/.test(value)) return value;
|
|
986
|
+
let query = `SELECT ad_group.id, ad_group.name FROM ad_group WHERE ad_group.name = '${value.replace(/'/g, "\\'")}' LIMIT 1`;
|
|
987
|
+
if (campaignId) query = query.replace("LIMIT 1", `AND campaign.id = ${campaignId} LIMIT 1`);
|
|
988
|
+
const chunks = await googleAdsQuery(customerId, query);
|
|
989
|
+
const rows = chunks.flatMap((c) => c.results ?? []);
|
|
990
|
+
if (rows.length === 0) throw new Error(`Ad group not found by name: "${value}"`);
|
|
991
|
+
return rows[0].adGroup.id;
|
|
992
|
+
}
|
|
961
993
|
function formatMicros(micros) {
|
|
962
994
|
if (!micros) return "\u2014";
|
|
963
995
|
const val = parseInt(micros, 10);
|
|
@@ -1091,7 +1123,7 @@ function registerGetGoogleAdsCampaigns(server2) {
|
|
|
1091
1123
|
const header = `## Google Campaigns (${rows.length} found)
|
|
1092
1124
|
|
|
1093
1125
|
`;
|
|
1094
|
-
const tableHeader = "| Campaign | Status | Type | Bid Strategy | Target | Daily Budget | App |\n
|
|
1126
|
+
const tableHeader = "| ID | Campaign | Status | Type | Bid Strategy | Target | Daily Budget | App |\n|---|---|---|---|---|---|---|---|\n";
|
|
1095
1127
|
let tableRows = "";
|
|
1096
1128
|
for (const row of rows) {
|
|
1097
1129
|
const c = row.campaign;
|
|
@@ -1102,7 +1134,7 @@ function registerGetGoogleAdsCampaigns(server2) {
|
|
|
1102
1134
|
const appId = c.appCampaignSetting?.appId ?? "";
|
|
1103
1135
|
const store = appStoreLabel(c.appCampaignSetting?.appStore);
|
|
1104
1136
|
const appDisplay = appId ? `${appId} (${store})` : "\u2014";
|
|
1105
|
-
tableRows += `| ${c.name} | ${c.status} | ${channelSubTypeLabel(c.advertisingChannelSubType)} | ${c.biddingStrategyType ?? "\u2014"} | ${formatTarget(row)} | ${budgetDisplay} | ${appDisplay} |
|
|
1137
|
+
tableRows += `| ${c.id} | ${c.name} | ${c.status} | ${channelSubTypeLabel(c.advertisingChannelSubType)} | ${c.biddingStrategyType ?? "\u2014"} | ${formatTarget(row)} | ${budgetDisplay} | ${appDisplay} |
|
|
1106
1138
|
`;
|
|
1107
1139
|
}
|
|
1108
1140
|
return {
|
|
@@ -1143,8 +1175,9 @@ function registerGetGoogleAdsAdGroups(server2) {
|
|
|
1143
1175
|
let whereClause = `WHERE campaign.advertising_channel_sub_type IN ('APP_CAMPAIGN', 'APP_CAMPAIGN_FOR_ENGAGEMENT')
|
|
1144
1176
|
AND ad_group.status IN (${statusList})`;
|
|
1145
1177
|
if (campaign_id) {
|
|
1178
|
+
const resolvedCampaignId = await resolveCampaignId(customer_id, campaign_id);
|
|
1146
1179
|
whereClause += `
|
|
1147
|
-
AND campaign.id = ${
|
|
1180
|
+
AND campaign.id = ${resolvedCampaignId}`;
|
|
1148
1181
|
}
|
|
1149
1182
|
const query = `
|
|
1150
1183
|
SELECT
|
|
@@ -1175,13 +1208,13 @@ function registerGetGoogleAdsAdGroups(server2) {
|
|
|
1175
1208
|
const header = `## Ad Groups${campaignName} (${rows.length} found)
|
|
1176
1209
|
|
|
1177
1210
|
`;
|
|
1178
|
-
const tableHeader = "| Ad Group | Status | Type | Campaign |\n
|
|
1211
|
+
const tableHeader = "| Ad Group ID | Ad Group | Status | Type | Campaign ID | Campaign |\n|---|---|---|---|---|---|\n";
|
|
1179
1212
|
let tableRows = "";
|
|
1180
1213
|
for (const row of rows) {
|
|
1181
1214
|
const ag = row.adGroup;
|
|
1182
1215
|
const c = row.campaign;
|
|
1183
1216
|
if (!ag) continue;
|
|
1184
|
-
tableRows += `| ${ag.name} | ${ag.status ?? "\u2014"} | ${ag.type ?? "\u2014"} | ${c?.name ?? "\u2014"} |
|
|
1217
|
+
tableRows += `| ${ag.id} | ${ag.name} | ${ag.status ?? "\u2014"} | ${ag.type ?? "\u2014"} | ${c?.id ?? "\u2014"} | ${c?.name ?? "\u2014"} |
|
|
1185
1218
|
`;
|
|
1186
1219
|
}
|
|
1187
1220
|
return {
|
|
@@ -1236,8 +1269,14 @@ function registerGetGoogleAdsAssets(server2) {
|
|
|
1236
1269
|
const conditions = [
|
|
1237
1270
|
"campaign.status = 'ENABLED'"
|
|
1238
1271
|
];
|
|
1239
|
-
if (campaign_id)
|
|
1240
|
-
|
|
1272
|
+
if (campaign_id) {
|
|
1273
|
+
const resolvedCampaignId = await resolveCampaignId(customer_id, campaign_id);
|
|
1274
|
+
conditions.push(`campaign.id = ${resolvedCampaignId}`);
|
|
1275
|
+
}
|
|
1276
|
+
if (ad_group_id) {
|
|
1277
|
+
const resolvedAdGroupId = await resolveAdGroupId(customer_id, ad_group_id, campaign_id);
|
|
1278
|
+
conditions.push(`ad_group.id = ${resolvedAdGroupId}`);
|
|
1279
|
+
}
|
|
1241
1280
|
if (asset_type?.length) {
|
|
1242
1281
|
const typeList = asset_type.map((t) => `'${t}'`).join(", ");
|
|
1243
1282
|
conditions.push(`asset.type IN (${typeList})`);
|
|
@@ -1531,8 +1570,14 @@ function registerGetGoogleAdsInsights(server2) {
|
|
|
1531
1570
|
"campaign.status = 'ENABLED'",
|
|
1532
1571
|
`segments.date BETWEEN '${startDate}' AND '${endDate}'`
|
|
1533
1572
|
];
|
|
1534
|
-
if (campaign_id)
|
|
1535
|
-
|
|
1573
|
+
if (campaign_id) {
|
|
1574
|
+
const resolvedCampaignId = await resolveCampaignId(customer_id, campaign_id);
|
|
1575
|
+
conditions.push(`campaign.id = ${resolvedCampaignId}`);
|
|
1576
|
+
}
|
|
1577
|
+
if (ad_group_id) {
|
|
1578
|
+
const resolvedAdGroupId = await resolveAdGroupId(customer_id, ad_group_id, campaign_id);
|
|
1579
|
+
conditions.push(`ad_group.id = ${resolvedAdGroupId}`);
|
|
1580
|
+
}
|
|
1536
1581
|
const orderBy = `ORDER BY ${sortKeyForEnum(sortField)} DESC`;
|
|
1537
1582
|
const query = `
|
|
1538
1583
|
SELECT ${selectFields.join(", ")}
|
|
@@ -1677,7 +1722,10 @@ function registerGetGoogleAdsNetworkMix(server2) {
|
|
|
1677
1722
|
"campaign.status = 'ENABLED'",
|
|
1678
1723
|
`segments.date BETWEEN '${startDate}' AND '${endDate}'`
|
|
1679
1724
|
];
|
|
1680
|
-
if (campaign_id)
|
|
1725
|
+
if (campaign_id) {
|
|
1726
|
+
const resolvedCampaignId = await resolveCampaignId(customer_id, campaign_id);
|
|
1727
|
+
conditions.push(`campaign.id = ${resolvedCampaignId}`);
|
|
1728
|
+
}
|
|
1681
1729
|
const query = `
|
|
1682
1730
|
SELECT
|
|
1683
1731
|
campaign.id,
|
|
@@ -1887,11 +1935,15 @@ function registerGetGoogleAdsAssetFatigue(server2) {
|
|
|
1887
1935
|
startDate.setDate(startDate.getDate() - lookback + 1);
|
|
1888
1936
|
const ageStartDate = new Date(endDate);
|
|
1889
1937
|
ageStartDate.setDate(ageStartDate.getDate() - 89);
|
|
1938
|
+
const resolvedCampaignId = await resolveCampaignId(customer_id, campaign_id);
|
|
1890
1939
|
const conditions = [
|
|
1891
|
-
`campaign.id = ${
|
|
1940
|
+
`campaign.id = ${resolvedCampaignId}`,
|
|
1892
1941
|
`segments.date BETWEEN '${dateFmt(startDate)}' AND '${dateFmt(endDate)}'`
|
|
1893
1942
|
];
|
|
1894
|
-
if (ad_group_id)
|
|
1943
|
+
if (ad_group_id) {
|
|
1944
|
+
const resolvedAdGroupId = await resolveAdGroupId(customer_id, ad_group_id, resolvedCampaignId);
|
|
1945
|
+
conditions.push(`ad_group.id = ${resolvedAdGroupId}`);
|
|
1946
|
+
}
|
|
1895
1947
|
if (asset_type?.length) {
|
|
1896
1948
|
const typeList = asset_type.map((t) => `'${t}'`).join(", ");
|
|
1897
1949
|
conditions.push(`asset.type IN (${typeList})`);
|
|
@@ -2156,10 +2208,11 @@ function registerConnectionStatus(server2, status2) {
|
|
|
2156
2208
|
);
|
|
2157
2209
|
} else {
|
|
2158
2210
|
lines.push(
|
|
2159
|
-
"## Meta Marketing API: Not
|
|
2160
|
-
"-
|
|
2211
|
+
"## Meta Marketing API: Not Connected (Optional)",
|
|
2212
|
+
"- KB, suggestions, and private insights work without it",
|
|
2213
|
+
"- Connect Meta to unlock live campaign data and reports",
|
|
2161
2214
|
"",
|
|
2162
|
-
"### How to
|
|
2215
|
+
"### How to connect",
|
|
2163
2216
|
"Provide your Meta access token using one of these methods:",
|
|
2164
2217
|
'1. MCP config: add `"META_ACCESS_TOKEN": "..."` to the `"env"` block in `.mcp.json` (Claude Code/Cursor) or `claude_desktop_config.json` (Claude Desktop)',
|
|
2165
2218
|
"2. CLI argument: add `--meta-token=...` to the args array",
|
|
@@ -2176,11 +2229,11 @@ function registerConnectionStatus(server2, status2) {
|
|
|
2176
2229
|
);
|
|
2177
2230
|
} else {
|
|
2178
2231
|
lines.push(
|
|
2179
|
-
"## Google Ads API: Not
|
|
2180
|
-
"-
|
|
2181
|
-
|
|
2232
|
+
"## Google Ads API: Not Connected (Optional)",
|
|
2233
|
+
"- KB, suggestions, and private insights work without it",
|
|
2234
|
+
"- Connect Google Ads to unlock campaign data and network analysis",
|
|
2182
2235
|
"",
|
|
2183
|
-
"### How to
|
|
2236
|
+
"### How to connect",
|
|
2184
2237
|
"Option 1 \u2014 Interactive setup (recommended):",
|
|
2185
2238
|
"```",
|
|
2186
2239
|
"npx mobile-growth-mcp auth google",
|
|
@@ -2314,35 +2367,40 @@ function registerVocabularyResource(server2) {
|
|
|
2314
2367
|
}
|
|
2315
2368
|
|
|
2316
2369
|
// src/resources/instructions.ts
|
|
2317
|
-
var INSTRUCTIONS = `# Mobile Growth MCP \u2014 Knowledge Base +
|
|
2370
|
+
var INSTRUCTIONS = `# Mobile Growth MCP \u2014 Knowledge Base + Ad Platform Tools
|
|
2318
2371
|
|
|
2319
2372
|
## Welcome
|
|
2320
2373
|
|
|
2321
|
-
You're connected to the Mobile Growth knowledge base \u2014 curated expert insights on mobile advertising, campaign optimization, and subscription app growth.
|
|
2374
|
+
You're connected to the Mobile Growth knowledge base \u2014 curated expert insights on mobile advertising, campaign optimization, and subscription app growth \u2014 plus direct Meta and Google Ads API integration.
|
|
2322
2375
|
|
|
2323
2376
|
**The knowledge base is always on.** Use \`search_insights\` freely \u2014 before making recommendations, when diagnosing issues, or exploring strategies. The more specific your query, the better the results.
|
|
2324
2377
|
|
|
2378
|
+
> **Note:** Connecting Meta or Google Ads is optional. The knowledge base, community suggestions, and private insights all work with just your API key. Add Meta or Google Ads credentials later if you want live campaign data and reports.
|
|
2379
|
+
|
|
2325
2380
|
Quick examples:
|
|
2326
2381
|
- "subscription app creative fatigue signals"
|
|
2327
2382
|
- "Meta CBO vs ABO tradeoffs for subscription apps"
|
|
2383
|
+
- "Google UAC network shift detection"
|
|
2328
2384
|
- "iOS attribution strategies post-ATT"
|
|
2329
2385
|
|
|
2330
2386
|
If you can't find what you need, call \`submit_feedback\` to report the gap \u2014 it helps us improve the knowledge base.
|
|
2331
2387
|
|
|
2332
2388
|
## What This Is
|
|
2333
|
-
A curated knowledge base of mobile advertising insights + direct Meta Marketing API integration. Query expert knowledge, pull live campaign data, and run pre-built reports \u2014 all from your LLM.
|
|
2389
|
+
A curated knowledge base of mobile advertising insights + direct Meta Marketing API and Google Ads API integration. Query expert knowledge, pull live campaign data, and run pre-built reports \u2014 all from your LLM.
|
|
2390
|
+
|
|
2391
|
+
---
|
|
2334
2392
|
|
|
2335
2393
|
## Knowledge Base Tools
|
|
2336
2394
|
|
|
2337
2395
|
### search_insights
|
|
2338
|
-
Semantic + keyword hybrid search across the knowledge base.
|
|
2396
|
+
Semantic + keyword hybrid search across the knowledge base. **Use this first** for any question about mobile advertising before searching the web.
|
|
2339
2397
|
- **query** (required): Natural language search query
|
|
2340
2398
|
- **topics** (optional): Filter by topic tags, e.g. ["creative_strategy", "scaling"]
|
|
2341
2399
|
- **applies_to** (optional): Filter by applicability, e.g. ["subscription_apps", "ios"]
|
|
2342
2400
|
- **limit** (optional): Max results, 1-30, default 10
|
|
2343
2401
|
|
|
2344
2402
|
### list_insights
|
|
2345
|
-
Browse all insights with optional filtering. Returns titles and metadata.
|
|
2403
|
+
Browse all insights with optional filtering. Returns titles and metadata. Private insights are marked with a lock icon.
|
|
2346
2404
|
- **topic** (optional): Filter by a single topic tag
|
|
2347
2405
|
- **applies_to** (optional): Filter by a single applies_to value
|
|
2348
2406
|
|
|
@@ -2350,12 +2408,29 @@ Browse all insights with optional filtering. Returns titles and metadata.
|
|
|
2350
2408
|
Fetch the full content of a specific insight by ID or slug.
|
|
2351
2409
|
- **id** (required): Numeric ID or string slug (e.g. "mb-li-001")
|
|
2352
2410
|
|
|
2411
|
+
### get_vocabulary_counts
|
|
2412
|
+
Returns counts of how many insights use each topic and applies_to tag. Lightweight way to explore what's in the KB.
|
|
2413
|
+
|
|
2353
2414
|
### submit_feedback
|
|
2354
|
-
Report a gap in the knowledge base or a missing capability.
|
|
2355
|
-
- **category** (required): missing_knowledge, missing_feature, search_quality, or other
|
|
2356
|
-
- **summary** (required): What was needed
|
|
2415
|
+
Report a gap in the knowledge base, a bug in any tool, or a missing capability.
|
|
2416
|
+
- **category** (required): missing_knowledge, missing_feature, search_quality, bug_report, or other
|
|
2417
|
+
- **summary** (required): What was needed or what went wrong (anonymized \u2014 no account IDs or tokens)
|
|
2357
2418
|
- **search_queries_tried** (optional): Search queries that returned poor/no results
|
|
2358
2419
|
|
|
2420
|
+
## Community Knowledge
|
|
2421
|
+
|
|
2422
|
+
### suggest_insight \u2B50 DEFAULT for saving knowledge
|
|
2423
|
+
Submit knowledge for admin review. **This is the default tool when a user wants to save an insight** \u2014 it contributes to the shared knowledge base that benefits all users. Extract as much structured data as possible from the source material \u2014 full insight schema (title, insight text, source metadata, topics, actionable steps). Once approved, it's added to the shared knowledge base.
|
|
2424
|
+
- Use this when the user shares an article, post, or discussion with valuable mobile growth knowledge
|
|
2425
|
+
- Keep raw_excerpt concise (under 500 chars) for reliability
|
|
2426
|
+
|
|
2427
|
+
### save_private_insight
|
|
2428
|
+
Save knowledge that is private to your API key. Immediately searchable but only visible to you. **Only use this instead of suggest_insight when** the content contains client-specific data, internal benchmarks, account metrics, or the user explicitly asks for private storage.
|
|
2429
|
+
- Same full schema as suggest_insight
|
|
2430
|
+
- No admin approval needed \u2014 saved instantly
|
|
2431
|
+
|
|
2432
|
+
---
|
|
2433
|
+
|
|
2359
2434
|
## Meta Marketing API Tools
|
|
2360
2435
|
|
|
2361
2436
|
**Requires META_ACCESS_TOKEN env var** \u2014 without it, these tools return a clear error. Knowledge base tools work with just API_KEY.
|
|
@@ -2399,33 +2474,87 @@ Built-in report: detect creative fatigue via frequency, CTR decline, CPA trends.
|
|
|
2399
2474
|
- **frequency_critical** (optional): default 5
|
|
2400
2475
|
- **ctr_decline_threshold** (optional): default 30%
|
|
2401
2476
|
|
|
2477
|
+
---
|
|
2478
|
+
|
|
2402
2479
|
## Google Ads Tools
|
|
2403
2480
|
|
|
2404
|
-
**Requires Google Ads credentials** \u2014 run \`npx mobile-growth-mcp auth google\` to set up interactively.
|
|
2481
|
+
**Requires Google Ads credentials** \u2014 run \`npx mobile-growth-mcp auth google\` to set up interactively. Credentials are saved to \`.env\` and never leave the user's machine.
|
|
2482
|
+
|
|
2483
|
+
All tools accept campaign and ad group IDs as either **numeric IDs** or **campaign/ad group names** \u2014 names are auto-resolved to numeric IDs internally.
|
|
2405
2484
|
|
|
2406
2485
|
### get_google_ads_campaigns
|
|
2407
|
-
List
|
|
2486
|
+
List Google App Campaigns with status, bid strategy, budgets, and app info. Returns **numeric campaign IDs** needed by other tools.
|
|
2408
2487
|
- **customer_id** (required): Google Ads customer ID (e.g. "123-456-7890")
|
|
2409
|
-
- **status** (optional): Filter by status \u2014 ENABLED, PAUSED,
|
|
2488
|
+
- **status** (optional): Filter by status \u2014 ENABLED, PAUSED, REMOVED (default: ["ENABLED"])
|
|
2489
|
+
- **channel_sub_type** (optional): Filter by campaign type \u2014 APP_CAMPAIGN (ACi), APP_CAMPAIGN_FOR_ENGAGEMENT (ACe)
|
|
2410
2490
|
- **limit** (optional): Max campaigns to return (default 50)
|
|
2411
2491
|
|
|
2492
|
+
### get_google_ad_groups
|
|
2493
|
+
List ad groups within Google App Campaigns. Returns **numeric ad group IDs** and campaign IDs. In UAC, ad groups represent creative themes \u2014 observe spend distribution to identify winning messaging angles.
|
|
2494
|
+
- **customer_id** (required): Google Ads customer ID
|
|
2495
|
+
- **campaign_id** (optional): Scope to a specific campaign (name or numeric ID)
|
|
2496
|
+
- **status** (optional): Filter by status (default: ["ENABLED"])
|
|
2497
|
+
- **limit** (optional): Max results (default 50)
|
|
2498
|
+
|
|
2499
|
+
### get_google_insights
|
|
2500
|
+
Pull performance metrics with configurable level, breakdowns, date ranges, and time granularity. Use **network breakdown** to detect traffic shifts between Search, Display/AdMob, and YouTube \u2014 the #1 diagnostic lever for Google campaigns.
|
|
2501
|
+
- **customer_id** (required): Google Ads customer ID
|
|
2502
|
+
- **level** (optional): account, campaign, ad_group, asset (default: campaign)
|
|
2503
|
+
- **campaign_id** (optional): Scope to specific campaign (name or numeric ID)
|
|
2504
|
+
- **ad_group_id** (optional): Scope to specific ad group
|
|
2505
|
+
- **breakdown** (optional): network or device (one at a time \u2014 GAQL restriction)
|
|
2506
|
+
- **date_range** (optional): {start_date, end_date} in YYYY-MM-DD
|
|
2507
|
+
- **date_preset** (optional): LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH
|
|
2508
|
+
- **time_increment** (optional): daily, weekly, monthly, summary (default: summary)
|
|
2509
|
+
- **sort** (optional): cost_desc, conversions_desc, impressions_desc, ctr_desc
|
|
2510
|
+
- **limit** (optional): Max results (default 50, max 500)
|
|
2511
|
+
|
|
2512
|
+
### get_google_assets
|
|
2513
|
+
List creative assets with metadata, performance labels, and slot utilization audit. Checks headlines, descriptions, images, videos against Google's per-slot maximums.
|
|
2514
|
+
- **customer_id** (required): Google Ads customer ID
|
|
2515
|
+
- **campaign_id** (optional): Scope to a specific campaign (name or numeric ID)
|
|
2516
|
+
- **ad_group_id** (optional): Scope to a specific ad group
|
|
2517
|
+
- **asset_type** (optional): IMAGE, YOUTUBE_VIDEO, TEXT, MEDIA_BUNDLE
|
|
2518
|
+
- **include_slot_audit** (optional): default true
|
|
2519
|
+
- **limit** (optional): Max results (default 50)
|
|
2520
|
+
|
|
2521
|
+
### get_google_network_mix
|
|
2522
|
+
Analyze traffic distribution across Google's ad networks (Search, Display/AdMob, YouTube) over time. Flags significant shifts in spend share \u2014 a sudden shift to Display/MGDN typically tanks CPA.
|
|
2523
|
+
- **customer_id** (required): Google Ads customer ID
|
|
2524
|
+
- **campaign_id** (optional): Scope to one campaign (name or numeric ID)
|
|
2525
|
+
- **date_range** (optional): {start_date, end_date} \u2014 default last 14 days
|
|
2526
|
+
- **shift_threshold_pct** (optional): Flag networks with spend share change > this % (default 10)
|
|
2527
|
+
|
|
2528
|
+
### get_google_asset_fatigue
|
|
2529
|
+
Detect creative asset fatigue by analyzing per-asset impression trends, CTR decline, and CPA deterioration. Checks asset age against Google's 2-week learning minimum and 2-3 month refresh cadence.
|
|
2530
|
+
- **customer_id** (required): Google Ads customer ID
|
|
2531
|
+
- **campaign_id** (required): Campaign to analyze (name or numeric ID)
|
|
2532
|
+
- **ad_group_id** (optional): Scope to specific ad group
|
|
2533
|
+
- **lookback_days** (optional): Days of data to analyze, 7-90 (default 14)
|
|
2534
|
+
- **ctr_decline_threshold_pct** (optional): CTR decline % to flag (default 30)
|
|
2535
|
+
- **impression_decay_threshold_pct** (optional): Impression drop % to flag (default 50)
|
|
2536
|
+
- **asset_type** (optional): IMAGE, YOUTUBE_VIDEO, TEXT
|
|
2537
|
+
|
|
2538
|
+
---
|
|
2539
|
+
|
|
2412
2540
|
## Reports (MCP Prompts)
|
|
2413
2541
|
|
|
2414
|
-
Pre-built analysis workflows. Select a prompt and provide your ad_account_id to run:
|
|
2542
|
+
Pre-built analysis workflows for Meta accounts. Select a prompt and provide your ad_account_id to run:
|
|
2415
2543
|
|
|
2416
2544
|
| Prompt | What it does | API calls |
|
|
2417
2545
|
|--------|-------------|-----------|
|
|
2418
2546
|
| ad-fatigue-report | Detect creative fatigue with daily granularity | 1 |
|
|
2419
2547
|
| weekly-performance | Week-over-week health comparison with diagnosis | 2 |
|
|
2420
2548
|
| creative-performance | Categorize ads by health status | 1 |
|
|
2421
|
-
|
|
|
2422
|
-
| audience-composition | Age \xD7 gender heatmap with CPA analysis | 1-2 |
|
|
2549
|
+
| audience-composition | Age x gender heatmap with CPA analysis | 1-2 |
|
|
2423
2550
|
| architecture-review | Campaign structure evaluation | 3 (no insights) |
|
|
2424
2551
|
| audit-meta-account | Comprehensive account audit | 6+ |
|
|
2425
2552
|
| campaign-comparison | Side-by-side campaign comparison | 3+ |
|
|
2426
2553
|
| placement-audit | Detailed placement audit with examples | 1 per campaign |
|
|
2427
2554
|
| attribution-analysis | Conversion quality validation | 2+ |
|
|
2428
2555
|
|
|
2556
|
+
---
|
|
2557
|
+
|
|
2429
2558
|
## Resources
|
|
2430
2559
|
|
|
2431
2560
|
### vocabulary://tags
|
|
@@ -2435,17 +2564,31 @@ Lists all topic tags, applies_to tags, and platforms with counts.
|
|
|
2435
2564
|
|
|
2436
2565
|
When your response draws on knowledge base results, **always attribute visibly** so the user knows the value came from the curated KB, not your general training data:
|
|
2437
2566
|
|
|
2438
|
-
- **Tell the user** the information comes from the Mobile Growth knowledge base (e.g. "According to the Mobile Growth KB
|
|
2439
|
-
- **Cite source author + slug** for key claims (e.g. "
|
|
2440
|
-
- **When multiple insights support a recommendation**, mention the count (e.g. "3 insights in the KB agree that
|
|
2441
|
-
- **Distinguish KB-sourced advice from your own reasoning** \u2014 if you're adding your own analysis on top of KB results, make that clear
|
|
2442
|
-
|
|
2443
|
-
## Tips
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2567
|
+
- **Tell the user** the information comes from the Mobile Growth knowledge base (e.g. "According to the Mobile Growth KB..." or "The knowledge base recommends...")
|
|
2568
|
+
- **Cite source author + slug** for key claims (e.g. "...(source: Eric Seufert, \`mb-li-001\`)")
|
|
2569
|
+
- **When multiple insights support a recommendation**, mention the count (e.g. "3 insights in the KB agree that...")
|
|
2570
|
+
- **Distinguish KB-sourced advice from your own reasoning** \u2014 if you're adding your own analysis on top of KB results, make that clear
|
|
2571
|
+
|
|
2572
|
+
## Workflow Tips
|
|
2573
|
+
|
|
2574
|
+
### For Google Ads analysis:
|
|
2575
|
+
1. Start with \`get_google_ads_campaigns\` to see campaigns and get numeric IDs
|
|
2576
|
+
2. Use \`get_google_insights\` with network breakdown to check traffic distribution
|
|
2577
|
+
3. Use \`get_google_network_mix\` if you suspect network shifts
|
|
2578
|
+
4. Use \`get_google_assets\` to audit creative slot utilization
|
|
2579
|
+
5. Use \`get_google_asset_fatigue\` on specific campaigns to detect creative decay
|
|
2580
|
+
|
|
2581
|
+
### For Meta analysis:
|
|
2582
|
+
1. Start with \`get_meta_campaigns\` to see account structure
|
|
2583
|
+
2. Use reports (MCP prompts) for comprehensive analysis
|
|
2584
|
+
3. For custom analysis, use \`get_meta_insights\` with breakdowns
|
|
2585
|
+
|
|
2586
|
+
### General:
|
|
2587
|
+
- Always \`search_insights\` before making recommendations \u2014 ground advice in expert knowledge
|
|
2588
|
+
- Use \`get_insight\` to read full context when reports reference insight IDs
|
|
2589
|
+
- Use \`suggest_insight\` (default) when a user shares valuable knowledge from articles or discussions
|
|
2590
|
+
- Use \`save_private_insight\` only for client-specific data the user explicitly wants private
|
|
2591
|
+
- If a tool errors unexpectedly, call \`submit_feedback\` with category \`bug_report\`
|
|
2449
2592
|
`;
|
|
2450
2593
|
function buildStatusSection(status2) {
|
|
2451
2594
|
if (!status2) return "";
|
|
@@ -2466,20 +2609,20 @@ function buildStatusSection(status2) {
|
|
|
2466
2609
|
lines.push("- **Meta Marketing API**: Token configured");
|
|
2467
2610
|
} else {
|
|
2468
2611
|
lines.push(
|
|
2469
|
-
"- **Meta Marketing API**:
|
|
2612
|
+
"- **Meta Marketing API**: Not connected (optional \u2014 KB works without it)"
|
|
2470
2613
|
);
|
|
2471
2614
|
lines.push(
|
|
2472
|
-
" -
|
|
2615
|
+
" - To connect: provide your token via `--meta-token=...` CLI arg, `META_ACCESS_TOKEN` env var, or `.env` file"
|
|
2473
2616
|
);
|
|
2474
2617
|
}
|
|
2475
2618
|
if (status2.google.configured) {
|
|
2476
2619
|
lines.push("- **Google Ads API**: Configured");
|
|
2477
2620
|
} else {
|
|
2478
2621
|
lines.push(
|
|
2479
|
-
"- **Google Ads API**: Not
|
|
2622
|
+
"- **Google Ads API**: Not connected (optional \u2014 KB works without it)"
|
|
2480
2623
|
);
|
|
2481
2624
|
lines.push(
|
|
2482
|
-
" -
|
|
2625
|
+
" - To connect: run `npx mobile-growth-mcp auth google` to set up credentials"
|
|
2483
2626
|
);
|
|
2484
2627
|
}
|
|
2485
2628
|
return lines.join("\n");
|
|
@@ -2788,7 +2931,7 @@ async function runGoogleAuthFlow() {
|
|
|
2788
2931
|
});
|
|
2789
2932
|
try {
|
|
2790
2933
|
console.log("Step 1: Developer token");
|
|
2791
|
-
console.log(" (Found in Google Ads \u2192
|
|
2934
|
+
console.log(" (Found in Google Ads \u2192 Admin \u2192 API Center, or https://ads.google.com/aw/apicenter)\n");
|
|
2792
2935
|
const developerToken = await prompt(rl, " Developer token: ");
|
|
2793
2936
|
if (!developerToken) {
|
|
2794
2937
|
console.error("\n Developer token is required.");
|
|
@@ -2876,10 +3019,10 @@ console.error(
|
|
|
2876
3019
|
apiKey ? `API key: ${apiKeyResult.source}` : "API key: not configured \u2014 KB tools will not be available"
|
|
2877
3020
|
);
|
|
2878
3021
|
console.error(
|
|
2879
|
-
metaTokenResult.value ? `Meta token: ${metaTokenResult.source}` : "Meta token: not configured \u2014
|
|
3022
|
+
metaTokenResult.value ? `Meta token: ${metaTokenResult.source}` : "Meta token: not configured (optional \u2014 KB works without it)"
|
|
2880
3023
|
);
|
|
2881
3024
|
console.error(
|
|
2882
|
-
googleAdsResult.configured ? `Google Ads: configured` : `Google Ads: not configured (
|
|
3025
|
+
googleAdsResult.configured ? `Google Ads: configured` : `Google Ads: not configured (optional \u2014 KB works without it). Run \`npx mobile-growth-mcp auth google\` to set up`
|
|
2883
3026
|
);
|
|
2884
3027
|
var server = new McpServer({
|
|
2885
3028
|
name: "mobile-growth-mcp",
|
package/package.json
CHANGED