apphud-mcp 0.1.2 → 0.1.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/README.md +17 -17
- package/dist/src/domain/constants.js +42 -42
- package/dist/src/mcp/server.js +28 -28
- package/dist/src/services/analyticsService.js +16 -13
- package/dist/src/services/apphudClient.js +90 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,26 +46,26 @@ Zero-config MCP server for Apphud Dashboard Analytics.
|
|
|
46
46
|
|
|
47
47
|
Ask MCP to call:
|
|
48
48
|
|
|
49
|
-
1. `
|
|
50
|
-
2. `
|
|
51
|
-
3. `
|
|
49
|
+
1. `apphud_apps_list`
|
|
50
|
+
2. `apphud_analytics_capabilities_get` (with `app_id` from step 1)
|
|
51
|
+
3. `apphud_analytics_revenue_summary` (for a date range)
|
|
52
52
|
|
|
53
53
|
## Available Tools
|
|
54
54
|
|
|
55
|
-
- `
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
59
|
-
- `
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
55
|
+
- `apphud_apps_list`
|
|
56
|
+
- `apphud_analytics_events_list`
|
|
57
|
+
- `apphud_analytics_active_subscriptions`
|
|
58
|
+
- `apphud_analytics_capabilities_get`
|
|
59
|
+
- `apphud_analytics_metrics_list`
|
|
60
|
+
- `apphud_analytics_metric_value`
|
|
61
|
+
- `apphud_analytics_metric_timeseries`
|
|
62
|
+
- `apphud_analytics_metric_breakdown`
|
|
63
|
+
- `apphud_analytics_revenue_summary`
|
|
64
|
+
- `apphud_analytics_subscriptions_summary`
|
|
65
|
+
- `apphud_analytics_conversion_trial_to_paid`
|
|
66
|
+
- `apphud_analytics_cohorts_retention`
|
|
67
|
+
- `apphud_analytics_cohorts_ltv`
|
|
68
|
+
- `apphud_analytics_query_raw`
|
|
69
69
|
|
|
70
70
|
## Development
|
|
71
71
|
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
export const TOOL_PERMISSIONS = {
|
|
2
2
|
analyst: new Set([
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
3
|
+
"apphud_apps_list",
|
|
4
|
+
"apphud_analytics_events_list",
|
|
5
|
+
"apphud_analytics_active_subscriptions",
|
|
6
|
+
"apphud_analytics_capabilities_get",
|
|
7
|
+
"apphud_analytics_metrics_list",
|
|
8
|
+
"apphud_analytics_metric_value",
|
|
9
|
+
"apphud_analytics_metric_timeseries",
|
|
10
|
+
"apphud_analytics_metric_breakdown",
|
|
11
|
+
"apphud_analytics_revenue_summary",
|
|
12
|
+
"apphud_analytics_subscriptions_summary",
|
|
13
|
+
"apphud_analytics_conversion_trial_to_paid",
|
|
14
|
+
"apphud_analytics_cohorts_retention",
|
|
15
|
+
"apphud_analytics_cohorts_ltv",
|
|
16
|
+
"apphud_analytics_query_raw",
|
|
17
17
|
]),
|
|
18
18
|
support: new Set([
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
19
|
+
"apphud_apps_list",
|
|
20
|
+
"apphud_analytics_events_list",
|
|
21
|
+
"apphud_analytics_active_subscriptions",
|
|
22
|
+
"apphud_analytics_capabilities_get",
|
|
23
|
+
"apphud_analytics_metrics_list",
|
|
24
|
+
"apphud_analytics_metric_value",
|
|
25
|
+
"apphud_analytics_metric_timeseries",
|
|
26
|
+
"apphud_analytics_metric_breakdown",
|
|
27
|
+
"apphud_analytics_revenue_summary",
|
|
28
|
+
"apphud_analytics_subscriptions_summary",
|
|
29
|
+
"apphud_analytics_conversion_trial_to_paid",
|
|
30
|
+
"apphud_analytics_cohorts_retention",
|
|
31
|
+
"apphud_analytics_cohorts_ltv",
|
|
32
|
+
"apphud_analytics_query_raw",
|
|
33
33
|
]),
|
|
34
34
|
admin: new Set([
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
35
|
+
"apphud_apps_list",
|
|
36
|
+
"apphud_analytics_events_list",
|
|
37
|
+
"apphud_analytics_active_subscriptions",
|
|
38
|
+
"apphud_analytics_capabilities_get",
|
|
39
|
+
"apphud_analytics_metrics_list",
|
|
40
|
+
"apphud_analytics_metric_value",
|
|
41
|
+
"apphud_analytics_metric_timeseries",
|
|
42
|
+
"apphud_analytics_metric_breakdown",
|
|
43
|
+
"apphud_analytics_revenue_summary",
|
|
44
|
+
"apphud_analytics_subscriptions_summary",
|
|
45
|
+
"apphud_analytics_conversion_trial_to_paid",
|
|
46
|
+
"apphud_analytics_cohorts_retention",
|
|
47
|
+
"apphud_analytics_cohorts_ltv",
|
|
48
|
+
"apphud_analytics_query_raw",
|
|
49
49
|
]),
|
|
50
50
|
};
|
|
51
51
|
export const METRIC_REVENUE_EVENT_TYPES = [
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -34,14 +34,14 @@ export function createMcpServer(container) {
|
|
|
34
34
|
name: "apphud-mcp",
|
|
35
35
|
version: "0.2.0",
|
|
36
36
|
});
|
|
37
|
-
server.tool("
|
|
37
|
+
server.tool("apphud_apps_list", "List apps available in authenticated Apphud dashboard account", {
|
|
38
38
|
tenant_id: z.string().optional(),
|
|
39
39
|
auth: authSchema,
|
|
40
40
|
include_raw: z.boolean().optional(),
|
|
41
41
|
}, async (input) => {
|
|
42
42
|
const auth = resolveAuth(input, container.config);
|
|
43
43
|
try {
|
|
44
|
-
const result = await container.toolGuard.run(auth, "
|
|
44
|
+
const result = await container.toolGuard.run(auth, "apphud_apps_list", async () => container.analyticsService.appsList(auth, {
|
|
45
45
|
include_raw: input.include_raw,
|
|
46
46
|
}));
|
|
47
47
|
return jsonResult(result);
|
|
@@ -50,7 +50,7 @@ export function createMcpServer(container) {
|
|
|
50
50
|
return jsonResult(toToolError(error), true);
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
server.tool("
|
|
53
|
+
server.tool("apphud_analytics_events_list", "List raw subscription events from Apphud dashboard event feed", {
|
|
54
54
|
tenant_id: z.string().optional(),
|
|
55
55
|
auth: authSchema,
|
|
56
56
|
app_id: z.string().min(1).optional(),
|
|
@@ -64,7 +64,7 @@ export function createMcpServer(container) {
|
|
|
64
64
|
}, async (input) => {
|
|
65
65
|
const auth = resolveAuth(input, container.config);
|
|
66
66
|
try {
|
|
67
|
-
const result = await container.toolGuard.run(auth, "
|
|
67
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_events_list", async () => container.analyticsService.eventsList(auth, {
|
|
68
68
|
app_id: input.app_id,
|
|
69
69
|
apphud_app_id: input.apphud_app_id,
|
|
70
70
|
from: input.from,
|
|
@@ -80,7 +80,7 @@ export function createMcpServer(container) {
|
|
|
80
80
|
return jsonResult(toToolError(error), true);
|
|
81
81
|
}
|
|
82
82
|
});
|
|
83
|
-
server.tool("
|
|
83
|
+
server.tool("apphud_analytics_active_subscriptions", "Fetch active paid subscriptions directly from Apphud analytics endpoint", {
|
|
84
84
|
tenant_id: z.string().optional(),
|
|
85
85
|
auth: authSchema,
|
|
86
86
|
app_id: z.string().min(1).optional(),
|
|
@@ -90,7 +90,7 @@ export function createMcpServer(container) {
|
|
|
90
90
|
}, async (input) => {
|
|
91
91
|
const auth = resolveAuth(input, container.config);
|
|
92
92
|
try {
|
|
93
|
-
const result = await container.toolGuard.run(auth, "
|
|
93
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_active_subscriptions", async () => container.analyticsService.activeSubscriptions(auth, {
|
|
94
94
|
app_id: input.app_id,
|
|
95
95
|
apphud_app_id: input.apphud_app_id,
|
|
96
96
|
platform: input.platform,
|
|
@@ -102,7 +102,7 @@ export function createMcpServer(container) {
|
|
|
102
102
|
return jsonResult(toToolError(error), true);
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
|
-
server.tool("
|
|
105
|
+
server.tool("apphud_analytics_capabilities_get", "Get analytics capabilities and available metrics", {
|
|
106
106
|
tenant_id: z.string().optional(),
|
|
107
107
|
auth: authSchema,
|
|
108
108
|
app_id: z.string().min(1).optional(),
|
|
@@ -111,7 +111,7 @@ export function createMcpServer(container) {
|
|
|
111
111
|
}, async (input) => {
|
|
112
112
|
const auth = resolveAuth(input, container.config);
|
|
113
113
|
try {
|
|
114
|
-
const result = await container.toolGuard.run(auth, "
|
|
114
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_capabilities_get", async () => container.analyticsService.capabilities(auth, {
|
|
115
115
|
app_id: input.app_id,
|
|
116
116
|
apphud_app_id: input.apphud_app_id,
|
|
117
117
|
include_raw: input.include_raw,
|
|
@@ -122,7 +122,7 @@ export function createMcpServer(container) {
|
|
|
122
122
|
return jsonResult(toToolError(error), true);
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
|
-
server.tool("
|
|
125
|
+
server.tool("apphud_analytics_metrics_list", "List analytics metric keys", {
|
|
126
126
|
tenant_id: z.string().optional(),
|
|
127
127
|
auth: authSchema,
|
|
128
128
|
app_id: z.string().min(1).optional(),
|
|
@@ -131,7 +131,7 @@ export function createMcpServer(container) {
|
|
|
131
131
|
}, async (input) => {
|
|
132
132
|
const auth = resolveAuth(input, container.config);
|
|
133
133
|
try {
|
|
134
|
-
const result = await container.toolGuard.run(auth, "
|
|
134
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_metrics_list", async () => container.analyticsService.metricsList(auth, {
|
|
135
135
|
app_id: input.app_id,
|
|
136
136
|
apphud_app_id: input.apphud_app_id,
|
|
137
137
|
include_raw: input.include_raw,
|
|
@@ -142,7 +142,7 @@ export function createMcpServer(container) {
|
|
|
142
142
|
return jsonResult(toToolError(error), true);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
|
-
server.tool("
|
|
145
|
+
server.tool("apphud_analytics_metric_value", "Get single metric value for period/snapshot", {
|
|
146
146
|
tenant_id: z.string().optional(),
|
|
147
147
|
auth: authSchema,
|
|
148
148
|
app_id: z.string().min(1).optional(),
|
|
@@ -156,7 +156,7 @@ export function createMcpServer(container) {
|
|
|
156
156
|
}, async (input) => {
|
|
157
157
|
const auth = resolveAuth(input, container.config);
|
|
158
158
|
try {
|
|
159
|
-
const result = await container.toolGuard.run(auth, "
|
|
159
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_metric_value", async () => container.analyticsService.metricValue(auth, {
|
|
160
160
|
app_id: input.app_id,
|
|
161
161
|
apphud_app_id: input.apphud_app_id,
|
|
162
162
|
metric_key: input.metric_key,
|
|
@@ -172,7 +172,7 @@ export function createMcpServer(container) {
|
|
|
172
172
|
return jsonResult(toToolError(error), true);
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
|
-
server.tool("
|
|
175
|
+
server.tool("apphud_analytics_metric_timeseries", "Get metric timeseries by date range", {
|
|
176
176
|
tenant_id: z.string().optional(),
|
|
177
177
|
auth: authSchema,
|
|
178
178
|
app_id: z.string().min(1).optional(),
|
|
@@ -187,7 +187,7 @@ export function createMcpServer(container) {
|
|
|
187
187
|
}, async (input) => {
|
|
188
188
|
const auth = resolveAuth(input, container.config);
|
|
189
189
|
try {
|
|
190
|
-
const result = await container.toolGuard.run(auth, "
|
|
190
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_metric_timeseries", async () => container.analyticsService.metricTimeseries(auth, {
|
|
191
191
|
app_id: input.app_id,
|
|
192
192
|
apphud_app_id: input.apphud_app_id,
|
|
193
193
|
metric_key: input.metric_key,
|
|
@@ -204,7 +204,7 @@ export function createMcpServer(container) {
|
|
|
204
204
|
return jsonResult(toToolError(error), true);
|
|
205
205
|
}
|
|
206
206
|
});
|
|
207
|
-
server.tool("
|
|
207
|
+
server.tool("apphud_analytics_metric_breakdown", "Get metric breakdown by dimension", {
|
|
208
208
|
tenant_id: z.string().optional(),
|
|
209
209
|
auth: authSchema,
|
|
210
210
|
app_id: z.string().min(1).optional(),
|
|
@@ -221,7 +221,7 @@ export function createMcpServer(container) {
|
|
|
221
221
|
}, async (input) => {
|
|
222
222
|
const auth = resolveAuth(input, container.config);
|
|
223
223
|
try {
|
|
224
|
-
const result = await container.toolGuard.run(auth, "
|
|
224
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_metric_breakdown", async () => container.analyticsService.metricBreakdown(auth, {
|
|
225
225
|
app_id: input.app_id,
|
|
226
226
|
apphud_app_id: input.apphud_app_id,
|
|
227
227
|
metric_key: input.metric_key,
|
|
@@ -240,7 +240,7 @@ export function createMcpServer(container) {
|
|
|
240
240
|
return jsonResult(toToolError(error), true);
|
|
241
241
|
}
|
|
242
242
|
});
|
|
243
|
-
server.tool("
|
|
243
|
+
server.tool("apphud_analytics_revenue_summary", "Get revenue summary for date range", {
|
|
244
244
|
tenant_id: z.string().optional(),
|
|
245
245
|
auth: authSchema,
|
|
246
246
|
app_id: z.string().min(1).optional(),
|
|
@@ -254,7 +254,7 @@ export function createMcpServer(container) {
|
|
|
254
254
|
}, async (input) => {
|
|
255
255
|
const auth = resolveAuth(input, container.config);
|
|
256
256
|
try {
|
|
257
|
-
const result = await container.toolGuard.run(auth, "
|
|
257
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_revenue_summary", async () => container.analyticsService.revenueSummary(auth, {
|
|
258
258
|
app_id: input.app_id,
|
|
259
259
|
apphud_app_id: input.apphud_app_id,
|
|
260
260
|
from: input.from,
|
|
@@ -270,7 +270,7 @@ export function createMcpServer(container) {
|
|
|
270
270
|
return jsonResult(toToolError(error), true);
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
|
-
server.tool("
|
|
273
|
+
server.tool("apphud_analytics_subscriptions_summary", "Get subscriptions KPI summary", {
|
|
274
274
|
tenant_id: z.string().optional(),
|
|
275
275
|
auth: authSchema,
|
|
276
276
|
app_id: z.string().min(1).optional(),
|
|
@@ -283,7 +283,7 @@ export function createMcpServer(container) {
|
|
|
283
283
|
}, async (input) => {
|
|
284
284
|
const auth = resolveAuth(input, container.config);
|
|
285
285
|
try {
|
|
286
|
-
const result = await container.toolGuard.run(auth, "
|
|
286
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_subscriptions_summary", async () => container.analyticsService.subscriptionsSummary(auth, {
|
|
287
287
|
app_id: input.app_id,
|
|
288
288
|
apphud_app_id: input.apphud_app_id,
|
|
289
289
|
from: input.from,
|
|
@@ -298,7 +298,7 @@ export function createMcpServer(container) {
|
|
|
298
298
|
return jsonResult(toToolError(error), true);
|
|
299
299
|
}
|
|
300
300
|
});
|
|
301
|
-
server.tool("
|
|
301
|
+
server.tool("apphud_analytics_conversion_trial_to_paid", "Get trial to paid conversion summary", {
|
|
302
302
|
tenant_id: z.string().optional(),
|
|
303
303
|
auth: authSchema,
|
|
304
304
|
app_id: z.string().min(1).optional(),
|
|
@@ -311,7 +311,7 @@ export function createMcpServer(container) {
|
|
|
311
311
|
}, async (input) => {
|
|
312
312
|
const auth = resolveAuth(input, container.config);
|
|
313
313
|
try {
|
|
314
|
-
const result = await container.toolGuard.run(auth, "
|
|
314
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_conversion_trial_to_paid", async () => container.analyticsService.conversionTrialToPaid(auth, {
|
|
315
315
|
app_id: input.app_id,
|
|
316
316
|
apphud_app_id: input.apphud_app_id,
|
|
317
317
|
from: input.from,
|
|
@@ -326,7 +326,7 @@ export function createMcpServer(container) {
|
|
|
326
326
|
return jsonResult(toToolError(error), true);
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
|
-
server.tool("
|
|
329
|
+
server.tool("apphud_analytics_cohorts_retention", "Get cohorts retention from analytics", {
|
|
330
330
|
tenant_id: z.string().optional(),
|
|
331
331
|
auth: authSchema,
|
|
332
332
|
app_id: z.string().min(1).optional(),
|
|
@@ -341,7 +341,7 @@ export function createMcpServer(container) {
|
|
|
341
341
|
}, async (input) => {
|
|
342
342
|
const auth = resolveAuth(input, container.config);
|
|
343
343
|
try {
|
|
344
|
-
const result = await container.toolGuard.run(auth, "
|
|
344
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_cohorts_retention", async () => container.analyticsService.cohortsRetention(auth, {
|
|
345
345
|
app_id: input.app_id,
|
|
346
346
|
apphud_app_id: input.apphud_app_id,
|
|
347
347
|
from: input.from,
|
|
@@ -358,7 +358,7 @@ export function createMcpServer(container) {
|
|
|
358
358
|
return jsonResult(toToolError(error), true);
|
|
359
359
|
}
|
|
360
360
|
});
|
|
361
|
-
server.tool("
|
|
361
|
+
server.tool("apphud_analytics_cohorts_ltv", "Get cohorts cumulative LTV from analytics", {
|
|
362
362
|
tenant_id: z.string().optional(),
|
|
363
363
|
auth: authSchema,
|
|
364
364
|
app_id: z.string().min(1).optional(),
|
|
@@ -373,7 +373,7 @@ export function createMcpServer(container) {
|
|
|
373
373
|
}, async (input) => {
|
|
374
374
|
const auth = resolveAuth(input, container.config);
|
|
375
375
|
try {
|
|
376
|
-
const result = await container.toolGuard.run(auth, "
|
|
376
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_cohorts_ltv", async () => container.analyticsService.cohortsLtv(auth, {
|
|
377
377
|
app_id: input.app_id,
|
|
378
378
|
apphud_app_id: input.apphud_app_id,
|
|
379
379
|
from: input.from,
|
|
@@ -390,7 +390,7 @@ export function createMcpServer(container) {
|
|
|
390
390
|
return jsonResult(toToolError(error), true);
|
|
391
391
|
}
|
|
392
392
|
});
|
|
393
|
-
server.tool("
|
|
393
|
+
server.tool("apphud_analytics_query_raw", "Low-level analytics query tool (value/timeseries/breakdown/raw)", {
|
|
394
394
|
tenant_id: z.string().optional(),
|
|
395
395
|
auth: authSchema,
|
|
396
396
|
app_id: z.string().min(1).optional(),
|
|
@@ -414,7 +414,7 @@ export function createMcpServer(container) {
|
|
|
414
414
|
}, async (input) => {
|
|
415
415
|
const auth = resolveAuth(input, container.config);
|
|
416
416
|
try {
|
|
417
|
-
const result = await container.toolGuard.run(auth, "
|
|
417
|
+
const result = await container.toolGuard.run(auth, "apphud_analytics_query_raw", async () => container.analyticsService.queryRaw(auth, {
|
|
418
418
|
app_id: input.app_id,
|
|
419
419
|
apphud_app_id: input.apphud_app_id,
|
|
420
420
|
query: input.query,
|
|
@@ -136,8 +136,11 @@ export class AnalyticsService {
|
|
|
136
136
|
}));
|
|
137
137
|
const hasMore = response.hasMore ?? (response.nextCursor ? true : undefined);
|
|
138
138
|
const warnings = [];
|
|
139
|
+
if (response.sourcePath === "event_feed_unavailable") {
|
|
140
|
+
warnings.push("Raw event feed endpoint is unavailable (404/405). Returning empty events list; use dashboard metric tools for aggregated event-type counts.");
|
|
141
|
+
}
|
|
139
142
|
if (events.length === 0) {
|
|
140
|
-
warnings.push("No events extracted from Apphud dashboard response. Try
|
|
143
|
+
warnings.push("No events extracted from Apphud dashboard response. Try apphud_analytics_query_raw with include_raw=true to inspect endpoint payload.");
|
|
141
144
|
}
|
|
142
145
|
return {
|
|
143
146
|
app_id: app.appId,
|
|
@@ -196,17 +199,17 @@ export class AnalyticsService {
|
|
|
196
199
|
analytics_available: true,
|
|
197
200
|
source: "apphud_analytics_api",
|
|
198
201
|
supported_tools: [
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
"
|
|
202
|
+
"apphud_analytics_events_list",
|
|
203
|
+
"apphud_analytics_metrics_list",
|
|
204
|
+
"apphud_analytics_metric_value",
|
|
205
|
+
"apphud_analytics_metric_timeseries",
|
|
206
|
+
"apphud_analytics_metric_breakdown",
|
|
207
|
+
"apphud_analytics_revenue_summary",
|
|
208
|
+
"apphud_analytics_subscriptions_summary",
|
|
209
|
+
"apphud_analytics_conversion_trial_to_paid",
|
|
210
|
+
"apphud_analytics_cohorts_retention",
|
|
211
|
+
"apphud_analytics_cohorts_ltv",
|
|
212
|
+
"apphud_analytics_query_raw",
|
|
210
213
|
],
|
|
211
214
|
supported_shapes: ["value", "timeseries", "breakdown", "raw"],
|
|
212
215
|
metrics_count: metrics.metrics.length,
|
|
@@ -770,7 +773,7 @@ export class AnalyticsService {
|
|
|
770
773
|
}
|
|
771
774
|
throw new ApphudMcpError("INVALID_PAYLOAD", "app_id is required when account has multiple apps", {
|
|
772
775
|
statusCode: 400,
|
|
773
|
-
actionHint: "Call
|
|
776
|
+
actionHint: "Call apphud_apps_list and pass app_id from returned list",
|
|
774
777
|
details: {
|
|
775
778
|
app_ids: discovered.apps.slice(0, 50).map((app) => app.app_id),
|
|
776
779
|
},
|
|
@@ -386,11 +386,10 @@ function isLikelyMetricIdentifier(value) {
|
|
|
386
386
|
}
|
|
387
387
|
export function extractMetricValue(payload, metricKey) {
|
|
388
388
|
const aliases = expandMetricAliases(metricKey);
|
|
389
|
-
const
|
|
390
|
-
const search = (node, path) => {
|
|
389
|
+
const search = (node, path, mode, visited) => {
|
|
391
390
|
if (Array.isArray(node)) {
|
|
392
391
|
for (let index = 0; index < node.length; index += 1) {
|
|
393
|
-
const found = search(node[index], `${path}[${index}]
|
|
392
|
+
const found = search(node[index], `${path}[${index}]`, mode, visited);
|
|
394
393
|
if (found) {
|
|
395
394
|
return found;
|
|
396
395
|
}
|
|
@@ -411,10 +410,6 @@ export function extractMetricValue(payload, metricKey) {
|
|
|
411
410
|
return { value: metricValue, path };
|
|
412
411
|
}
|
|
413
412
|
}
|
|
414
|
-
const directValue = readMetricValueFromRecord(node, aliases);
|
|
415
|
-
if (directValue !== null && path !== "$") {
|
|
416
|
-
return { value: directValue, path };
|
|
417
|
-
}
|
|
418
413
|
for (const [key, value] of Object.entries(node)) {
|
|
419
414
|
if (aliases.has(normalizeMetricKey(key))) {
|
|
420
415
|
if (isRecord(value)) {
|
|
@@ -425,20 +420,34 @@ export function extractMetricValue(payload, metricKey) {
|
|
|
425
420
|
}
|
|
426
421
|
const direct = parseNumericValue(value);
|
|
427
422
|
if (direct !== null) {
|
|
428
|
-
return { value: direct, path
|
|
423
|
+
return { value: direct, path };
|
|
429
424
|
}
|
|
430
425
|
}
|
|
431
426
|
}
|
|
432
427
|
for (const [key, value] of Object.entries(node)) {
|
|
433
|
-
const found = search(value, `${path}.${key}
|
|
428
|
+
const found = search(value, `${path}.${key}`, mode, visited);
|
|
434
429
|
if (found) {
|
|
435
430
|
return found;
|
|
436
431
|
}
|
|
437
432
|
}
|
|
433
|
+
if (mode === "relaxed" && path !== "$") {
|
|
434
|
+
const hasConflictingNamedMetric = Boolean(namedMetric && !aliases.has(namedMetric));
|
|
435
|
+
const looksLikeTimeseriesPoint = extractDateFromRecord(node) !== null;
|
|
436
|
+
if (!hasConflictingNamedMetric && !looksLikeTimeseriesPoint) {
|
|
437
|
+
const directValue = readMetricValueFromRecord(node, aliases);
|
|
438
|
+
if (directValue !== null) {
|
|
439
|
+
return { value: directValue, path };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
438
443
|
return null;
|
|
439
444
|
};
|
|
440
|
-
const
|
|
441
|
-
|
|
445
|
+
const strictExtracted = search(payload, "$", "strict", new WeakSet());
|
|
446
|
+
if (strictExtracted) {
|
|
447
|
+
return strictExtracted;
|
|
448
|
+
}
|
|
449
|
+
const relaxedExtracted = search(payload, "$", "relaxed", new WeakSet());
|
|
450
|
+
return relaxedExtracted ? relaxedExtracted : { value: null };
|
|
442
451
|
}
|
|
443
452
|
export function extractActiveSubscriptionsValue(payload) {
|
|
444
453
|
return extractMetricValue(payload, "active_subs");
|
|
@@ -1053,6 +1062,55 @@ export class ApphudClient {
|
|
|
1053
1062
|
const apphudAppId = options.apphudAppId ?? app.appId;
|
|
1054
1063
|
const limit = Math.min(Math.max(options.limit ?? 100, 1), 500);
|
|
1055
1064
|
const normalizedEventType = normalizeEventType(options.eventType);
|
|
1065
|
+
const toDateOnly = (value) => {
|
|
1066
|
+
const parsed = new Date(value);
|
|
1067
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1068
|
+
return value.slice(0, 10);
|
|
1069
|
+
}
|
|
1070
|
+
return parsed.toISOString().slice(0, 10);
|
|
1071
|
+
};
|
|
1072
|
+
const cursorOffset = (() => {
|
|
1073
|
+
if (!options.cursor) {
|
|
1074
|
+
return 0;
|
|
1075
|
+
}
|
|
1076
|
+
const parsed = Number(options.cursor);
|
|
1077
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1078
|
+
return 0;
|
|
1079
|
+
}
|
|
1080
|
+
return Math.floor(parsed);
|
|
1081
|
+
})();
|
|
1082
|
+
const dashboardFields = ["event_name", "time", "paywall", "placement", "product", "revenue", "integrations_sent", "user_id"];
|
|
1083
|
+
const dashboardEventsQuery = new URLSearchParams({
|
|
1084
|
+
order: "desc",
|
|
1085
|
+
sort_by: "created_at",
|
|
1086
|
+
q: "",
|
|
1087
|
+
limit: String(limit),
|
|
1088
|
+
sandbox: "0",
|
|
1089
|
+
offset: String(cursorOffset),
|
|
1090
|
+
start_time: toDateOnly(options.from),
|
|
1091
|
+
end_time: toDateOnly(options.to),
|
|
1092
|
+
date_picker_type: "event_occured_in",
|
|
1093
|
+
});
|
|
1094
|
+
for (const field of dashboardFields) {
|
|
1095
|
+
dashboardEventsQuery.append("fields[]", field);
|
|
1096
|
+
}
|
|
1097
|
+
if (normalizedEventType) {
|
|
1098
|
+
dashboardEventsQuery.set("event_type", normalizedEventType);
|
|
1099
|
+
dashboardEventsQuery.set("kind", normalizedEventType);
|
|
1100
|
+
}
|
|
1101
|
+
if (options.userId) {
|
|
1102
|
+
dashboardEventsQuery.set("user_id", options.userId);
|
|
1103
|
+
}
|
|
1104
|
+
if (options.productId) {
|
|
1105
|
+
dashboardEventsQuery.set("product_id", options.productId);
|
|
1106
|
+
}
|
|
1107
|
+
if (options.country) {
|
|
1108
|
+
dashboardEventsQuery.set("country", options.country);
|
|
1109
|
+
}
|
|
1110
|
+
if (options.platform) {
|
|
1111
|
+
dashboardEventsQuery.set("platform", options.platform);
|
|
1112
|
+
}
|
|
1113
|
+
const dashboardEventsPath = `/apps/${encodeURIComponent(apphudAppId)}/events?${dashboardEventsQuery.toString()}`;
|
|
1056
1114
|
const filters = this.normalizeFilters({
|
|
1057
1115
|
event_type: normalizedEventType,
|
|
1058
1116
|
user_id: options.userId,
|
|
@@ -1125,13 +1183,22 @@ export class ApphudClient {
|
|
|
1125
1183
|
queryBase.platform = options.platform;
|
|
1126
1184
|
}
|
|
1127
1185
|
const attempts = [
|
|
1186
|
+
{ path: dashboardEventsPath, method: "GET" },
|
|
1128
1187
|
{ path: "/api/v1/events/list", method: "POST", body: bodyBase },
|
|
1129
1188
|
{ path: "/api/v1/events", method: "POST", body: bodyBase },
|
|
1130
1189
|
{ path: "/api/v1/subscribers/events", method: "POST", body: bodyBase },
|
|
1190
|
+
{ path: "/api/v1/events/feed", method: "POST", body: bodyBase },
|
|
1191
|
+
{ path: "/api/v1/feed/events", method: "POST", body: bodyBase },
|
|
1192
|
+
{ path: "/api/v1/activity/events", method: "POST", body: bodyBase },
|
|
1131
1193
|
{ path: "/api/v1/events/list", method: "GET", query: queryBase },
|
|
1132
1194
|
{ path: "/api/v1/events", method: "GET", query: queryBase },
|
|
1195
|
+
{ path: "/api/v1/events/feed", method: "GET", query: queryBase },
|
|
1196
|
+
{ path: "/api/v1/feed/events", method: "GET", query: queryBase },
|
|
1197
|
+
{ path: "/api/v1/activity/events", method: "GET", query: queryBase },
|
|
1133
1198
|
{ path: "/events/list", method: "GET", query: queryBase },
|
|
1134
1199
|
{ path: "/events", method: "GET", query: queryBase },
|
|
1200
|
+
{ path: "/events/feed", method: "GET", query: queryBase },
|
|
1201
|
+
{ path: "/feed/events", method: "GET", query: queryBase },
|
|
1135
1202
|
];
|
|
1136
1203
|
let firstSuccess;
|
|
1137
1204
|
let lastError;
|
|
@@ -1172,6 +1239,18 @@ export class ApphudClient {
|
|
|
1172
1239
|
return firstSuccess;
|
|
1173
1240
|
}
|
|
1174
1241
|
if (lastError) {
|
|
1242
|
+
const status = getAnalyticsStatusFromError(lastError);
|
|
1243
|
+
if (status === 404 || status === 405) {
|
|
1244
|
+
return {
|
|
1245
|
+
apphudAppId,
|
|
1246
|
+
events: [],
|
|
1247
|
+
sourcePath: "event_feed_unavailable",
|
|
1248
|
+
rawPayload: {
|
|
1249
|
+
reason: "all event feed endpoint candidates returned 404/405",
|
|
1250
|
+
attempted_paths: attempts.map((attempt) => `${attempt.method} ${attempt.path}`),
|
|
1251
|
+
},
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1175
1254
|
throw lastError;
|
|
1176
1255
|
}
|
|
1177
1256
|
return {
|