mobile-growth-mcp 2.0.3 → 2.0.8

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 +33 -2
  2. package/dist/index.js +195 -41
  3. package/package.json +9 -1
package/README.md CHANGED
@@ -12,7 +12,26 @@ You'll need an API key — get one from the person who set up your account.
12
12
 
13
13
  ## Setup
14
14
 
15
- Add this to your MCP client config (Claude Desktop, Cursor, Claude Code, Codex, etc.):
15
+ There are three ways to provide your API key and Meta token. The server checks them in this order:
16
+
17
+ ### Option 1: CLI arguments
18
+
19
+ Pass keys directly in the `args` array. Works with every MCP client.
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "mobile-growth": {
25
+ "command": "npx",
26
+ "args": ["-y", "mobile-growth-mcp", "--api-key=me_your-api-key", "--meta-token=your-meta-access-token"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### Option 2: Environment variables
33
+
34
+ Use the `env` block in your MCP client config.
16
35
 
17
36
  ```json
18
37
  {
@@ -29,7 +48,18 @@ Add this to your MCP client config (Claude Desktop, Cursor, Claude Code, Codex,
29
48
  }
30
49
  ```
31
50
 
32
- - `API_KEY` (required) authenticates with the knowledge base. You'll get this when your account is created.
51
+ ### Option 3: `.env` file
52
+
53
+ Create a `.env` file in your working directory:
54
+
55
+ ```
56
+ API_KEY=me_your-api-key
57
+ META_ACCESS_TOKEN=your-meta-access-token
58
+ ```
59
+
60
+ ### Keys
61
+
62
+ - `API_KEY` — authenticates with the knowledge base. Without it, the server still starts but only Meta tools are available. Call the `connection_status` tool to see what's connected.
33
63
  - `META_ACCESS_TOKEN` (optional) — enables Meta Marketing API tools. Without it, knowledge base tools still work.
34
64
 
35
65
  ## What you get
@@ -44,6 +74,7 @@ Add this to your MCP client config (Claude Desktop, Cursor, Claude Code, Codex,
44
74
 
45
75
  | Tool | Description |
46
76
  |------|-------------|
77
+ | `connection_status` | Check KB and Meta API connection status; shows how to fix issues |
47
78
  | `search_insights` | Semantic + keyword hybrid search across curated insights |
48
79
  | `get_insight` | Full content of a specific insight by slug or ID |
49
80
  | `get_meta_campaigns` | List campaigns with objectives, budgets, bid strategies |
package/dist/index.js CHANGED
@@ -83,16 +83,7 @@ function jsonSchemaToZodShape(inputSchema) {
83
83
  }
84
84
  return shape;
85
85
  }
86
- async function registerRemoteTools(server2, apiKey2) {
87
- let tools;
88
- try {
89
- tools = await fetchRemoteTools(apiKey2);
90
- } catch (err) {
91
- console.error(
92
- `Failed to fetch remote tools: ${err.message}. KB tools will not be available.`
93
- );
94
- return;
95
- }
86
+ function registerFetchedTools(server2, apiKey2, tools) {
96
87
  for (const tool of tools) {
97
88
  const zodShape = jsonSchemaToZodShape(tool.inputSchema);
98
89
  server2.tool(tool.name, tool.description, zodShape, async (args) => {
@@ -124,17 +115,8 @@ async function getRemotePrompt(apiKey2, name, args) {
124
115
  }
125
116
  return resp.result?.messages ?? [];
126
117
  }
127
- async function registerRemotePrompts(server2, apiKey2) {
128
- let remotePrompts;
129
- try {
130
- remotePrompts = await fetchRemotePrompts(apiKey2);
131
- } catch (err) {
132
- console.error(
133
- `Failed to fetch remote prompts: ${err.message}. Prompts will not be available.`
134
- );
135
- return;
136
- }
137
- for (const prompt of remotePrompts) {
118
+ function registerFetchedPrompts(server2, apiKey2, prompts) {
119
+ for (const prompt of prompts) {
138
120
  const zodShape = {};
139
121
  for (const arg of prompt.arguments) {
140
122
  let field = z.string().describe(arg.description);
@@ -729,26 +711,26 @@ function registerGetMetaAdFatigue(server2) {
729
711
  const lateSpend = lateDays.reduce((s, d) => s + d.spend, 0);
730
712
  const recentCpa = lateConv > 0 ? lateSpend / lateConv : null;
731
713
  const cpaChange = earlyCpa !== null && recentCpa !== null && earlyCpa > 0 ? (recentCpa - earlyCpa) / earlyCpa * 100 : null;
732
- let status = "HEALTHY";
714
+ let status2 = "HEALTHY";
733
715
  let diagnosis = "Metrics stable \u2014 no fatigue detected.";
734
716
  const highFreq = avgFreq >= freqCrit;
735
717
  const medFreq = avgFreq >= freqWarn;
736
718
  const ctrDeclining = ctrDecline >= ctrThreshold;
737
719
  const cpaRising = cpaChange !== null && cpaChange > 20;
738
720
  if (highFreq && cpaRising) {
739
- status = "FATIGUED";
721
+ status2 = "FATIGUED";
740
722
  diagnosis = `Audience saturation: frequency ${avgFreq.toFixed(1)} + CPA rising ${cpaChange.toFixed(0)}% [wk-tw-001 #1, ds-pt-003]`;
741
723
  } else if (ctrDeclining && cpaRising) {
742
- status = "FATIGUED";
724
+ status2 = "FATIGUED";
743
725
  diagnosis = `Creative fatigue: CTR declined ${ctrDecline.toFixed(0)}% from peak + CPA rising ${cpaChange.toFixed(0)}% [wk-tw-001 #4]`;
744
726
  } else if (highFreq) {
745
- status = "WARNING";
727
+ status2 = "WARNING";
746
728
  diagnosis = `High frequency (${avgFreq.toFixed(1)}) \u2014 approaching saturation [ds-pt-003]. CPA ${cpaRising ? "rising" : "stable"}.`;
747
729
  } else if (ctrDeclining) {
748
- status = "WARNING";
730
+ status2 = "WARNING";
749
731
  diagnosis = `CTR declining ${ctrDecline.toFixed(0)}% from peak \u2014 early fatigue signal [wk-tw-001 #4].`;
750
732
  } else if (medFreq && cpaRising) {
751
- status = "WARNING";
733
+ status2 = "WARNING";
752
734
  diagnosis = `Frequency ${avgFreq.toFixed(1)} + CPA trending up ${cpaChange.toFixed(0)}% \u2014 monitor closely [wk-tw-001 #1].`;
753
735
  }
754
736
  results.push({
@@ -762,7 +744,7 @@ function registerGetMetaAdFatigue(server2) {
762
744
  early_cpa: earlyCpa,
763
745
  recent_cpa: recentCpa,
764
746
  cpa_change_pct: cpaChange,
765
- status,
747
+ status: status2,
766
748
  diagnosis
767
749
  });
768
750
  }
@@ -837,6 +819,58 @@ ${text}`;
837
819
  );
838
820
  }
839
821
 
822
+ // src/tools/connection-status.ts
823
+ function registerConnectionStatus(server2, status2) {
824
+ server2.tool(
825
+ "connection_status",
826
+ "Check the connection status of the knowledge base and Meta API. Call this if tools seem missing or you get unexpected errors.",
827
+ {},
828
+ async () => {
829
+ const lines = ["# Connection Status", ""];
830
+ if (status2.kb.connected) {
831
+ lines.push(
832
+ `## Knowledge Base: Connected`,
833
+ `- ${status2.kb.toolCount} KB tools loaded`,
834
+ `- ${status2.kb.promptCount} prompts loaded`,
835
+ `- API key source: ${status2.apiKey.source}`
836
+ );
837
+ } else {
838
+ lines.push(
839
+ `## Knowledge Base: Not Connected`,
840
+ `- Reason: ${status2.kb.error ?? "API_KEY not configured"}`,
841
+ "",
842
+ "### How to fix",
843
+ "Provide your API key using one of these methods (in priority order):",
844
+ "1. CLI argument: add `--api-key=me_...` to the args array in your MCP config",
845
+ '2. Environment variable: add `"API_KEY": "me_..."` to the env block in your MCP config',
846
+ "3. `.env` file: create a `.env` file in your working directory with `API_KEY=me_...`"
847
+ );
848
+ }
849
+ lines.push("");
850
+ if (status2.meta.tokenConfigured) {
851
+ lines.push(
852
+ "## Meta Marketing API: Configured",
853
+ "- Meta tools are available and ready to use"
854
+ );
855
+ } else {
856
+ lines.push(
857
+ "## Meta Marketing API: Not Configured",
858
+ "- Meta tools will return an error when called",
859
+ "",
860
+ "### How to fix",
861
+ "Provide your Meta access token using one of these methods:",
862
+ "1. CLI argument: add `--meta-token=...` to the args array",
863
+ '2. Environment variable: add `"META_ACCESS_TOKEN": "..."` to the env block',
864
+ "3. `.env` file: add `META_ACCESS_TOKEN=...` to your `.env` file"
865
+ );
866
+ }
867
+ return {
868
+ content: [{ type: "text", text: lines.join("\n") }]
869
+ };
870
+ }
871
+ );
872
+ }
873
+
840
874
  // ../shared/dist/types.js
841
875
  var VALID_PLATFORMS = [
842
876
  "meta",
@@ -1042,7 +1076,35 @@ Lists all topic tags, applies_to tags, and platforms with counts.
1042
1076
  - Reports reference specific knowledge base insight IDs \u2014 use \`get_insight\` to read the full context
1043
1077
  - For custom date ranges, use time_range instead of date_preset
1044
1078
  `;
1045
- function registerInstructionsResource(server2) {
1079
+ function buildStatusSection(status2) {
1080
+ if (!status2) return "";
1081
+ const lines = ["\n## Connection Status\n"];
1082
+ if (status2.kb.connected) {
1083
+ lines.push(
1084
+ `- **Knowledge Base**: Connected (${status2.kb.toolCount} tools, ${status2.kb.promptCount} prompts)`
1085
+ );
1086
+ } else {
1087
+ lines.push(
1088
+ `- **Knowledge Base**: Not connected \u2014 ${status2.kb.error ?? "API_KEY not configured"}`
1089
+ );
1090
+ lines.push(
1091
+ " - Fix: provide your API key via `--api-key=me_...` CLI arg, `API_KEY` env var, or `.env` file"
1092
+ );
1093
+ }
1094
+ if (status2.meta.tokenConfigured) {
1095
+ lines.push("- **Meta Marketing API**: Token configured");
1096
+ } else {
1097
+ lines.push(
1098
+ "- **Meta Marketing API**: Token not configured \u2014 Meta tools will return errors"
1099
+ );
1100
+ lines.push(
1101
+ " - Fix: provide your token via `--meta-token=...` CLI arg, `META_ACCESS_TOKEN` env var, or `.env` file"
1102
+ );
1103
+ }
1104
+ return lines.join("\n");
1105
+ }
1106
+ function registerInstructionsResource(server2, status2) {
1107
+ const text = INSTRUCTIONS + buildStatusSection(status2);
1046
1108
  server2.resource(
1047
1109
  "instructions",
1048
1110
  "instructions://getting-started",
@@ -1055,34 +1117,126 @@ function registerInstructionsResource(server2) {
1055
1117
  {
1056
1118
  uri: "instructions://getting-started",
1057
1119
  mimeType: "text/plain",
1058
- text: INSTRUCTIONS
1120
+ text
1059
1121
  }
1060
1122
  ]
1061
1123
  })
1062
1124
  );
1063
1125
  }
1064
1126
 
1065
- // src/index.ts
1066
- var apiKey = process.env.API_KEY;
1067
- if (!apiKey) {
1068
- console.error(
1069
- 'Error: API_KEY environment variable is required.\nGet your key from the server admin and add it to your MCP config:\n "env": { "API_KEY": "me_..." }'
1070
- );
1071
- process.exit(1);
1127
+ // src/config.ts
1128
+ import { readFileSync } from "fs";
1129
+ import { join } from "path";
1130
+ function parseEnvFile(path) {
1131
+ let content;
1132
+ try {
1133
+ content = readFileSync(path, "utf-8");
1134
+ } catch {
1135
+ return {};
1136
+ }
1137
+ const vars = {};
1138
+ for (const line of content.split("\n")) {
1139
+ const trimmed = line.trim();
1140
+ if (!trimmed || trimmed.startsWith("#")) continue;
1141
+ const eq = trimmed.indexOf("=");
1142
+ if (eq === -1) continue;
1143
+ const key = trimmed.slice(0, eq).trim();
1144
+ let value = trimmed.slice(eq + 1).trim();
1145
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1146
+ value = value.slice(1, -1);
1147
+ }
1148
+ vars[key] = value;
1149
+ }
1150
+ return vars;
1151
+ }
1152
+ function getCliArg(name) {
1153
+ const prefix = `--${name}=`;
1154
+ for (const arg of process.argv) {
1155
+ if (arg.startsWith(prefix)) {
1156
+ return arg.slice(prefix.length).trim();
1157
+ }
1158
+ }
1159
+ return void 0;
1160
+ }
1161
+ var dotEnvCache;
1162
+ function getDotEnv() {
1163
+ if (!dotEnvCache) {
1164
+ dotEnvCache = parseEnvFile(join(process.cwd(), ".env"));
1165
+ }
1166
+ return dotEnvCache;
1167
+ }
1168
+ function resolve(envName, cliName) {
1169
+ const cli = getCliArg(cliName);
1170
+ if (cli) return { value: cli, source: `--${cliName} argument` };
1171
+ const env = process.env[envName]?.trim();
1172
+ if (env) return { value: env, source: `${envName} env var` };
1173
+ const dotenv = getDotEnv()[envName];
1174
+ if (dotenv) return { value: dotenv, source: ".env file" };
1175
+ return { value: void 0, source: "not configured" };
1072
1176
  }
1177
+ function resolveApiKey() {
1178
+ return resolve("API_KEY", "api-key");
1179
+ }
1180
+ function resolveMetaToken() {
1181
+ return resolve("META_ACCESS_TOKEN", "meta-token");
1182
+ }
1183
+
1184
+ // src/index.ts
1185
+ var apiKeyResult = resolveApiKey();
1186
+ var metaTokenResult = resolveMetaToken();
1187
+ if (apiKeyResult.value) process.env.API_KEY = apiKeyResult.value;
1188
+ if (metaTokenResult.value) process.env.META_ACCESS_TOKEN = metaTokenResult.value;
1189
+ var apiKey = apiKeyResult.value;
1190
+ console.error(
1191
+ apiKey ? `API key: ${apiKeyResult.source}` : "API key: not configured \u2014 KB tools will not be available"
1192
+ );
1193
+ console.error(
1194
+ metaTokenResult.value ? `Meta token: ${metaTokenResult.source}` : "Meta token: not configured \u2014 Meta tools will return errors when called"
1195
+ );
1073
1196
  var server = new McpServer({
1074
1197
  name: "mobile-growth-mcp",
1075
1198
  version: "2.0.0"
1076
1199
  });
1077
- console.error("Connecting to knowledge base...");
1078
- await registerRemoteTools(server, apiKey);
1200
+ var status = {
1201
+ kb: { connected: false, toolCount: 0, promptCount: 0 },
1202
+ meta: { tokenConfigured: !!metaTokenResult.value },
1203
+ apiKey: { source: apiKeyResult.source }
1204
+ };
1205
+ if (apiKey) {
1206
+ console.error("Connecting to knowledge base...");
1207
+ try {
1208
+ const tools = await fetchRemoteTools(apiKey);
1209
+ registerFetchedTools(server, apiKey, tools);
1210
+ status.kb.toolCount = tools.length;
1211
+ status.kb.connected = true;
1212
+ console.error(`KB connected: ${tools.length} tools loaded`);
1213
+ } catch (err) {
1214
+ const msg = err.message;
1215
+ status.kb.error = msg;
1216
+ console.error(`KB tools failed: ${msg}`);
1217
+ }
1218
+ } else {
1219
+ status.kb.error = "API_KEY not configured";
1220
+ }
1079
1221
  registerGetMetaCampaigns(server);
1080
1222
  registerGetMetaAdSets(server);
1081
1223
  registerGetMetaAds(server);
1082
1224
  registerGetMetaInsights(server);
1083
1225
  registerGetMetaAdFatigue(server);
1226
+ registerConnectionStatus(server, status);
1084
1227
  registerVocabularyResource(server);
1085
- registerInstructionsResource(server);
1086
- await registerRemotePrompts(server, apiKey);
1228
+ registerInstructionsResource(server, status);
1229
+ if (apiKey) {
1230
+ try {
1231
+ const prompts = await fetchRemotePrompts(apiKey);
1232
+ registerFetchedPrompts(server, apiKey, prompts);
1233
+ status.kb.promptCount = prompts.length;
1234
+ console.error(`KB prompts: ${prompts.length} loaded`);
1235
+ } catch (err) {
1236
+ console.error(
1237
+ `KB prompts failed: ${err.message}`
1238
+ );
1239
+ }
1240
+ }
1087
1241
  var transport = new StdioServerTransport();
1088
1242
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobile-growth-mcp",
3
- "version": "2.0.3",
3
+ "version": "2.0.8",
4
4
  "description": "MCP server for mobile growth & UA knowledge base — campaign optimization, creative strategy, and subscription app insights",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,5 +33,13 @@
33
33
  "meta-ads",
34
34
  "subscription-apps"
35
35
  ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/kubachour/mobile-growth-mcp.git"
39
+ },
40
+ "homepage": "https://github.com/kubachour/mobile-growth-mcp#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/kubachour/mobile-growth-mcp/issues"
43
+ },
36
44
  "license": "MIT"
37
45
  }