apphud-mcp 0.2.4 → 0.2.5
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 +38 -14
- package/dist/src/cli.js +4 -4
- package/dist/src/domain/constants.js +19 -96
- package/dist/src/http/server.js +81 -1
- package/dist/src/mcp/server.js +14 -835
- package/dist/src/services/analyticsService.js +599 -145
- package/dist/src/services/apphudClient.js +80 -26
- package/dist/src/tools/remoteTools.js +398 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# apphud-mcp <img src="./assets/apphud-mcp-logo.svg" alt="apphud + mcp" height="28" />
|
|
2
2
|
|
|
3
|
-
MCP server for Apphud analytics
|
|
3
|
+
MCP server for Apphud dashboard analytics endpoints (apps, events, metrics, cohorts).
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -14,10 +14,7 @@ MCP server for Apphud analytics with storage-based ETL ingestion (GCS/S3) and lo
|
|
|
14
14
|
"args": ["-y", "apphud-mcp@0.2.2", "start"],
|
|
15
15
|
"env": {
|
|
16
16
|
"login": "your@apphud.email",
|
|
17
|
-
"password": "your_apphud_password"
|
|
18
|
-
"ETL_SOURCE": "gcs",
|
|
19
|
-
"ETL_GCS_BUCKET": "your-bucket",
|
|
20
|
-
"ETL_GCS_PREFIX": "apphud/exports"
|
|
17
|
+
"password": "your_apphud_password"
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
20
|
}
|
|
@@ -25,10 +22,30 @@ MCP server for Apphud analytics with storage-based ETL ingestion (GCS/S3) and lo
|
|
|
25
22
|
```
|
|
26
23
|
|
|
27
24
|
2. Restart MCP server.
|
|
28
|
-
3. Check
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
25
|
+
3. Check dashboard access:
|
|
26
|
+
- `apphud_apps_list`
|
|
27
|
+
- `apphud_analytics_events_list` (requires `from`/`to`)
|
|
28
|
+
- `apphud_analytics_metric_timeseries` (requires `metric_key`, `from`, `to`)
|
|
29
|
+
|
|
30
|
+
## HTTP Tool Calls
|
|
31
|
+
|
|
32
|
+
If `HTTP_ENABLED=true`, you can call tools over HTTP (stable payload path for web clients):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -sS -X POST http://localhost:8080/tools/call \
|
|
36
|
+
-H 'content-type: application/json' \
|
|
37
|
+
-d '{
|
|
38
|
+
"name":"apphud_analytics_metric_timeseries",
|
|
39
|
+
"arguments":{
|
|
40
|
+
"app_id":"your_app_id",
|
|
41
|
+
"metric_key":"revenue_gross",
|
|
42
|
+
"from":"2026-02-01T00:00:00.000Z",
|
|
43
|
+
"to":"2026-02-21T23:59:59.000Z"
|
|
44
|
+
}
|
|
45
|
+
}'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The endpoint also accepts `toolName` and stringified `arguments` JSON for compatibility with buggy wrappers.
|
|
32
49
|
|
|
33
50
|
## ETL Setup
|
|
34
51
|
|
|
@@ -83,11 +100,18 @@ MCP server for Apphud analytics with storage-based ETL ingestion (GCS/S3) and lo
|
|
|
83
100
|
|
|
84
101
|
## Useful Tools
|
|
85
102
|
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
103
|
+
- `apphud_apps_list`
|
|
104
|
+
- `apphud_analytics_events_list`
|
|
105
|
+
- `apphud_analytics_metrics_list`
|
|
106
|
+
- `apphud_analytics_metric_value`
|
|
107
|
+
- `apphud_analytics_metric_timeseries`
|
|
108
|
+
- `apphud_analytics_metric_breakdown`
|
|
109
|
+
- `apphud_analytics_revenue_summary`
|
|
110
|
+
- `apphud_analytics_subscriptions_summary`
|
|
111
|
+
- `apphud_analytics_conversion_trial_to_paid`
|
|
112
|
+
- `apphud_analytics_cohorts_retention`
|
|
113
|
+
- `apphud_analytics_cohorts_ltv`
|
|
114
|
+
- `apphud_analytics_query_raw`
|
|
91
115
|
|
|
92
116
|
## Development
|
|
93
117
|
|
package/dist/src/cli.js
CHANGED
|
@@ -40,10 +40,10 @@ Zero-config quick start:
|
|
|
40
40
|
2) Set env vars:
|
|
41
41
|
login=<your apphud email>
|
|
42
42
|
password=<your apphud password>
|
|
43
|
-
3) Use
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
3) Use analytics tools:
|
|
44
|
+
apphud_apps_list
|
|
45
|
+
apphud_analytics_events_list
|
|
46
|
+
apphud_analytics_metric_timeseries
|
|
47
47
|
|
|
48
48
|
Defaults:
|
|
49
49
|
- SQLite DB: .apphud-mcp/apphud.db
|
|
@@ -1,100 +1,23 @@
|
|
|
1
|
+
const ANALYTICS_TOOL_PERMISSIONS = [
|
|
2
|
+
"apphud_apps_list",
|
|
3
|
+
"apphud_analytics_events_list",
|
|
4
|
+
"apphud_analytics_active_subscriptions",
|
|
5
|
+
"apphud_analytics_capabilities_get",
|
|
6
|
+
"apphud_analytics_metrics_list",
|
|
7
|
+
"apphud_analytics_metric_value",
|
|
8
|
+
"apphud_analytics_metric_timeseries",
|
|
9
|
+
"apphud_analytics_metric_breakdown",
|
|
10
|
+
"apphud_analytics_revenue_summary",
|
|
11
|
+
"apphud_analytics_subscriptions_summary",
|
|
12
|
+
"apphud_analytics_conversion_trial_to_paid",
|
|
13
|
+
"apphud_analytics_cohorts_retention",
|
|
14
|
+
"apphud_analytics_cohorts_ltv",
|
|
15
|
+
"apphud_analytics_query_raw",
|
|
16
|
+
];
|
|
1
17
|
export const TOOL_PERMISSIONS = {
|
|
2
|
-
analyst: new Set(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"apphud_dashboard_local",
|
|
6
|
-
"apphud_analytics_events_list",
|
|
7
|
-
"apphud_analytics_events_list_local",
|
|
8
|
-
"apphud_analytics_active_subscriptions",
|
|
9
|
-
"apphud_analytics_active_subscriptions_local",
|
|
10
|
-
"apphud_analytics_capabilities_get",
|
|
11
|
-
"apphud_analytics_capabilities_get_local",
|
|
12
|
-
"apphud_analytics_metrics_list",
|
|
13
|
-
"apphud_analytics_metrics_list_local",
|
|
14
|
-
"apphud_analytics_metric_value",
|
|
15
|
-
"apphud_analytics_metric_value_local",
|
|
16
|
-
"apphud_analytics_metric_timeseries",
|
|
17
|
-
"apphud_analytics_metric_timeseries_local",
|
|
18
|
-
"apphud_analytics_metric_breakdown",
|
|
19
|
-
"apphud_analytics_metric_breakdown_local",
|
|
20
|
-
"apphud_analytics_revenue_summary",
|
|
21
|
-
"apphud_analytics_revenue_summary_local",
|
|
22
|
-
"apphud_analytics_subscriptions_summary",
|
|
23
|
-
"apphud_analytics_subscriptions_summary_local",
|
|
24
|
-
"apphud_analytics_conversion_trial_to_paid",
|
|
25
|
-
"apphud_analytics_conversion_trial_to_paid_local",
|
|
26
|
-
"apphud_analytics_cohorts_retention",
|
|
27
|
-
"apphud_analytics_cohorts_retention_local",
|
|
28
|
-
"apphud_analytics_cohorts_ltv",
|
|
29
|
-
"apphud_analytics_cohorts_ltv_local",
|
|
30
|
-
"apphud_analytics_query_raw",
|
|
31
|
-
"apphud_analytics_query_raw_local",
|
|
32
|
-
"apphud_etl_local_status",
|
|
33
|
-
]),
|
|
34
|
-
support: new Set([
|
|
35
|
-
"apphud_apps_list",
|
|
36
|
-
"apphud_apps_list_local",
|
|
37
|
-
"apphud_dashboard_local",
|
|
38
|
-
"apphud_analytics_events_list",
|
|
39
|
-
"apphud_analytics_events_list_local",
|
|
40
|
-
"apphud_analytics_active_subscriptions",
|
|
41
|
-
"apphud_analytics_active_subscriptions_local",
|
|
42
|
-
"apphud_analytics_capabilities_get",
|
|
43
|
-
"apphud_analytics_capabilities_get_local",
|
|
44
|
-
"apphud_analytics_metrics_list",
|
|
45
|
-
"apphud_analytics_metrics_list_local",
|
|
46
|
-
"apphud_analytics_metric_value",
|
|
47
|
-
"apphud_analytics_metric_value_local",
|
|
48
|
-
"apphud_analytics_metric_timeseries",
|
|
49
|
-
"apphud_analytics_metric_timeseries_local",
|
|
50
|
-
"apphud_analytics_metric_breakdown",
|
|
51
|
-
"apphud_analytics_metric_breakdown_local",
|
|
52
|
-
"apphud_analytics_revenue_summary",
|
|
53
|
-
"apphud_analytics_revenue_summary_local",
|
|
54
|
-
"apphud_analytics_subscriptions_summary",
|
|
55
|
-
"apphud_analytics_subscriptions_summary_local",
|
|
56
|
-
"apphud_analytics_conversion_trial_to_paid",
|
|
57
|
-
"apphud_analytics_conversion_trial_to_paid_local",
|
|
58
|
-
"apphud_analytics_cohorts_retention",
|
|
59
|
-
"apphud_analytics_cohorts_retention_local",
|
|
60
|
-
"apphud_analytics_cohorts_ltv",
|
|
61
|
-
"apphud_analytics_cohorts_ltv_local",
|
|
62
|
-
"apphud_analytics_query_raw",
|
|
63
|
-
"apphud_analytics_query_raw_local",
|
|
64
|
-
"apphud_etl_local_status",
|
|
65
|
-
]),
|
|
66
|
-
admin: new Set([
|
|
67
|
-
"apphud_apps_list",
|
|
68
|
-
"apphud_apps_list_local",
|
|
69
|
-
"apphud_dashboard_local",
|
|
70
|
-
"apphud_analytics_events_list",
|
|
71
|
-
"apphud_analytics_events_list_local",
|
|
72
|
-
"apphud_analytics_active_subscriptions",
|
|
73
|
-
"apphud_analytics_active_subscriptions_local",
|
|
74
|
-
"apphud_analytics_capabilities_get",
|
|
75
|
-
"apphud_analytics_capabilities_get_local",
|
|
76
|
-
"apphud_analytics_metrics_list",
|
|
77
|
-
"apphud_analytics_metrics_list_local",
|
|
78
|
-
"apphud_analytics_metric_value",
|
|
79
|
-
"apphud_analytics_metric_value_local",
|
|
80
|
-
"apphud_analytics_metric_timeseries",
|
|
81
|
-
"apphud_analytics_metric_timeseries_local",
|
|
82
|
-
"apphud_analytics_metric_breakdown",
|
|
83
|
-
"apphud_analytics_metric_breakdown_local",
|
|
84
|
-
"apphud_analytics_revenue_summary",
|
|
85
|
-
"apphud_analytics_revenue_summary_local",
|
|
86
|
-
"apphud_analytics_subscriptions_summary",
|
|
87
|
-
"apphud_analytics_subscriptions_summary_local",
|
|
88
|
-
"apphud_analytics_conversion_trial_to_paid",
|
|
89
|
-
"apphud_analytics_conversion_trial_to_paid_local",
|
|
90
|
-
"apphud_analytics_cohorts_retention",
|
|
91
|
-
"apphud_analytics_cohorts_retention_local",
|
|
92
|
-
"apphud_analytics_cohorts_ltv",
|
|
93
|
-
"apphud_analytics_cohorts_ltv_local",
|
|
94
|
-
"apphud_analytics_query_raw",
|
|
95
|
-
"apphud_analytics_query_raw_local",
|
|
96
|
-
"apphud_etl_local_status",
|
|
97
|
-
]),
|
|
18
|
+
analyst: new Set(ANALYTICS_TOOL_PERMISSIONS),
|
|
19
|
+
support: new Set(ANALYTICS_TOOL_PERMISSIONS),
|
|
20
|
+
admin: new Set(ANALYTICS_TOOL_PERMISSIONS),
|
|
98
21
|
};
|
|
99
22
|
export const METRIC_REVENUE_EVENT_TYPES = [
|
|
100
23
|
"trial_converted",
|
package/dist/src/http/server.js
CHANGED
|
@@ -1,11 +1,59 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
-
import { toToolError } from "../errors/toolError.js";
|
|
2
|
+
import { ApphudMcpError, isApphudMcpError, toToolError } from "../errors/toolError.js";
|
|
3
|
+
import { executeRemoteTool, isRemoteToolName, REMOTE_TOOL_NAMES } from "../tools/remoteTools.js";
|
|
3
4
|
function sendError(res, error) {
|
|
5
|
+
if (isApphudMcpError(error)) {
|
|
6
|
+
res.status(error.statusCode).json(toToolError(error));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
4
9
|
res.status(500).json(toToolError(error));
|
|
5
10
|
}
|
|
11
|
+
export function normalizeToolCallPayload(payload) {
|
|
12
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
13
|
+
throw new ApphudMcpError("INVALID_PAYLOAD", "Request body must be an object", { statusCode: 400 });
|
|
14
|
+
}
|
|
15
|
+
const body = payload;
|
|
16
|
+
const params = body.params;
|
|
17
|
+
const jsonRpcParams = params && typeof params === "object" && !Array.isArray(params) ? params : undefined;
|
|
18
|
+
const toolNameRaw = body.name ??
|
|
19
|
+
body.tool ??
|
|
20
|
+
body.toolName ??
|
|
21
|
+
jsonRpcParams?.name ??
|
|
22
|
+
jsonRpcParams?.tool ??
|
|
23
|
+
jsonRpcParams?.toolName;
|
|
24
|
+
if (typeof toolNameRaw !== "string" || toolNameRaw.trim().length === 0) {
|
|
25
|
+
throw new ApphudMcpError("INVALID_PAYLOAD", "tool name is required (name or toolName)", { statusCode: 400 });
|
|
26
|
+
}
|
|
27
|
+
const inputRaw = body.arguments ??
|
|
28
|
+
body.payload ??
|
|
29
|
+
body.input ??
|
|
30
|
+
jsonRpcParams?.arguments ??
|
|
31
|
+
jsonRpcParams?.payload ??
|
|
32
|
+
jsonRpcParams?.input;
|
|
33
|
+
if (inputRaw === undefined || inputRaw === null) {
|
|
34
|
+
return { toolName: toolNameRaw.trim(), input: {} };
|
|
35
|
+
}
|
|
36
|
+
if (typeof inputRaw === "string") {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(inputRaw);
|
|
39
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
40
|
+
throw new Error("arguments string must decode to object");
|
|
41
|
+
}
|
|
42
|
+
return { toolName: toolNameRaw.trim(), input: parsed };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new ApphudMcpError("INVALID_PAYLOAD", "arguments string must be valid JSON object", { statusCode: 400 });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!inputRaw || typeof inputRaw !== "object" || Array.isArray(inputRaw)) {
|
|
49
|
+
throw new ApphudMcpError("INVALID_PAYLOAD", "arguments must be an object", { statusCode: 400 });
|
|
50
|
+
}
|
|
51
|
+
return { toolName: toolNameRaw.trim(), input: inputRaw };
|
|
52
|
+
}
|
|
6
53
|
export function createHttpServer(container) {
|
|
7
54
|
const app = express();
|
|
8
55
|
app.disable("x-powered-by");
|
|
56
|
+
app.use(express.json({ limit: "1mb" }));
|
|
9
57
|
app.get("/health", async (_req, res) => {
|
|
10
58
|
res.status(200).json({ status: "ok" });
|
|
11
59
|
});
|
|
@@ -17,7 +65,39 @@ export function createHttpServer(container) {
|
|
|
17
65
|
timestamp: new Date().toISOString(),
|
|
18
66
|
});
|
|
19
67
|
});
|
|
68
|
+
async function handleToolCall(req, res) {
|
|
69
|
+
try {
|
|
70
|
+
const { toolName, input } = normalizeToolCallPayload(req.body);
|
|
71
|
+
if (!isRemoteToolName(toolName)) {
|
|
72
|
+
throw new ApphudMcpError("TOOL_NOT_FOUND", `Unsupported tool: ${toolName}`, {
|
|
73
|
+
statusCode: 404,
|
|
74
|
+
details: { supported_tools: REMOTE_TOOL_NAMES },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const result = await executeRemoteTool(container, toolName, input);
|
|
78
|
+
res.status(200).json({
|
|
79
|
+
ok: true,
|
|
80
|
+
tool: toolName,
|
|
81
|
+
result,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
sendError(res, error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
app.post("/tools/call", (req, res) => {
|
|
89
|
+
void handleToolCall(req, res);
|
|
90
|
+
});
|
|
91
|
+
app.post("/api/tools/call", (req, res) => {
|
|
92
|
+
void handleToolCall(req, res);
|
|
93
|
+
});
|
|
20
94
|
app.use((error, _req, res, _next) => {
|
|
95
|
+
if (error instanceof SyntaxError) {
|
|
96
|
+
sendError(res, new ApphudMcpError("INVALID_PAYLOAD", "Invalid JSON body", {
|
|
97
|
+
statusCode: 400,
|
|
98
|
+
}));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
21
101
|
sendError(res, error);
|
|
22
102
|
});
|
|
23
103
|
return app;
|