apphud-mcp 0.1.7 → 0.2.1
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 +37 -4
- package/dist/src/app.js +8 -3
- package/dist/src/config/env.js +51 -3
- package/dist/src/domain/constants.js +48 -0
- package/dist/src/mcp/server.js +419 -0
- package/dist/src/services/apphudClient.js +116 -0
- package/dist/src/services/etlService.js +1157 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
# apphud-mcp <img src="./assets/apphud-mcp-logo.svg" alt="apphud + mcp" height="28" />
|
|
2
2
|
|
|
3
|
-
Zero-config MCP server for Apphud Dashboard Analytics.
|
|
3
|
+
Zero-config MCP server for Apphud Dashboard Analytics with local SQLite storage.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
1. Optional config file (`apphud-mcp.config.json`):
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
|
-
{
|
|
10
|
+
{
|
|
11
|
+
"storage": {
|
|
12
|
+
"backend": "sqlite",
|
|
13
|
+
"sqlite_path": ".apphud-mcp/apphud.db"
|
|
14
|
+
},
|
|
15
|
+
"etl": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"tenant_id": "default",
|
|
18
|
+
"poll_interval_minutes": 60,
|
|
19
|
+
"storage_dir": ".apphud-etl",
|
|
20
|
+
"incoming_dir": ".apphud-etl/incoming",
|
|
21
|
+
"alerts_stale_hours": 30,
|
|
22
|
+
"remote_fetch_enabled": false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
11
25
|
```
|
|
12
26
|
|
|
13
27
|
2. Cursor MCP config:
|
|
@@ -37,10 +51,10 @@ Zero-config MCP server for Apphud Dashboard Analytics.
|
|
|
37
51
|
## What Changed
|
|
38
52
|
|
|
39
53
|
- Default mode is analytics-only.
|
|
40
|
-
- No
|
|
41
|
-
- No Postgres required.
|
|
54
|
+
- No Postgres required (SQLite is default).
|
|
42
55
|
- No webhook/event-store setup required.
|
|
43
56
|
- Apps are fetched directly from Apphud Dashboard API.
|
|
57
|
+
- ETL worker can run hourly and auto-sync app API keys from Dashboard (`settings/general` fallback).
|
|
44
58
|
|
|
45
59
|
## Basic Checks
|
|
46
60
|
|
|
@@ -52,6 +66,25 @@ Ask MCP to call:
|
|
|
52
66
|
|
|
53
67
|
## Available Tools
|
|
54
68
|
|
|
69
|
+
Local-first tools (higher priority):
|
|
70
|
+
- `apphud_etl_local_status`
|
|
71
|
+
- `apphud_apps_list_local`
|
|
72
|
+
- `apphud_dashboard_local`
|
|
73
|
+
- `apphud_analytics_events_list_local`
|
|
74
|
+
- `apphud_analytics_active_subscriptions_local`
|
|
75
|
+
- `apphud_analytics_capabilities_get_local`
|
|
76
|
+
- `apphud_analytics_metrics_list_local`
|
|
77
|
+
- `apphud_analytics_metric_value_local`
|
|
78
|
+
- `apphud_analytics_metric_timeseries_local`
|
|
79
|
+
- `apphud_analytics_metric_breakdown_local`
|
|
80
|
+
- `apphud_analytics_revenue_summary_local`
|
|
81
|
+
- `apphud_analytics_subscriptions_summary_local`
|
|
82
|
+
- `apphud_analytics_conversion_trial_to_paid_local`
|
|
83
|
+
- `apphud_analytics_cohorts_retention_local`
|
|
84
|
+
- `apphud_analytics_cohorts_ltv_local`
|
|
85
|
+
- `apphud_analytics_query_raw_local`
|
|
86
|
+
|
|
87
|
+
Remote tools (still available):
|
|
55
88
|
- `apphud_apps_list`
|
|
56
89
|
- `apphud_analytics_events_list`
|
|
57
90
|
- `apphud_analytics_active_subscriptions`
|
package/dist/src/app.js
CHANGED
|
@@ -4,6 +4,7 @@ import { RateLimiter } from "./security/rateLimiter.js";
|
|
|
4
4
|
import { AppService } from "./services/appService.js";
|
|
5
5
|
import { AnalyticsService } from "./services/analyticsService.js";
|
|
6
6
|
import { ApphudClient } from "./services/apphudClient.js";
|
|
7
|
+
import { EtlService } from "./services/etlService.js";
|
|
7
8
|
import { AuditService } from "./services/auditService.js";
|
|
8
9
|
import { ToolGuard } from "./services/toolGuard.js";
|
|
9
10
|
export function createServiceContainer(options) {
|
|
@@ -12,6 +13,7 @@ export function createServiceContainer(options) {
|
|
|
12
13
|
const appService = new AppService();
|
|
13
14
|
const apphudClient = new ApphudClient(config, secretStore);
|
|
14
15
|
const analyticsService = new AnalyticsService(appService, apphudClient);
|
|
16
|
+
const etlService = new EtlService(config, apphudClient);
|
|
15
17
|
const auditService = new AuditService(config);
|
|
16
18
|
const rateLimiter = new RateLimiter(config.rateLimitPerMinute);
|
|
17
19
|
const toolGuard = new ToolGuard(rateLimiter, auditService);
|
|
@@ -21,11 +23,14 @@ export function createServiceContainer(options) {
|
|
|
21
23
|
appService,
|
|
22
24
|
apphudClient,
|
|
23
25
|
analyticsService,
|
|
26
|
+
etlService,
|
|
24
27
|
auditService,
|
|
25
28
|
toolGuard,
|
|
26
|
-
close: async () =>
|
|
29
|
+
close: async () => {
|
|
30
|
+
await etlService.close();
|
|
31
|
+
},
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
|
-
export async function initializeServiceContainer(
|
|
30
|
-
|
|
34
|
+
export async function initializeServiceContainer(container) {
|
|
35
|
+
await container.etlService.start();
|
|
31
36
|
}
|
package/dist/src/config/env.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import "dotenv/config";
|
|
4
4
|
const VALID_ROLES = new Set(["analyst", "support", "admin"]);
|
|
5
|
-
const VALID_STORAGE_BACKENDS = new Set(["postgres", "memory"]);
|
|
5
|
+
const VALID_STORAGE_BACKENDS = new Set(["sqlite", "postgres", "memory"]);
|
|
6
6
|
const VALID_APP_STATUSES = new Set(["active", "disabled"]);
|
|
7
7
|
function parseNumber(value, fallback) {
|
|
8
8
|
if (value === undefined || value === null || value === "") {
|
|
@@ -148,7 +148,7 @@ export function loadEnvConfig(options = {}) {
|
|
|
148
148
|
const databaseUrlRaw = simpleConfig.database_url ?? process.env.DATABASE_URL;
|
|
149
149
|
const databaseUrl = typeof databaseUrlRaw === "string" && databaseUrlRaw.trim().length > 0 ? databaseUrlRaw : undefined;
|
|
150
150
|
const parsedStorageBackend = parseStorageBackend(simpleConfig.storage?.backend) ?? parseStorageBackend(process.env.STORAGE_BACKEND);
|
|
151
|
-
const storageBackend = parsedStorageBackend ?? (databaseUrl ? "postgres" : "
|
|
151
|
+
const storageBackend = parsedStorageBackend ?? (databaseUrl ? "postgres" : "sqlite");
|
|
152
152
|
if (storageBackend === "postgres" && !databaseUrl) {
|
|
153
153
|
throw new Error("DATABASE_URL is required for postgres storage backend. Set database_url or switch to storage.backend=memory");
|
|
154
154
|
}
|
|
@@ -178,11 +178,36 @@ export function loadEnvConfig(options = {}) {
|
|
|
178
178
|
const analyticsLoginPasswordSecretRef = typeof analyticsLoginPasswordSecretRefRaw === "string" && analyticsLoginPasswordSecretRefRaw.trim().length > 0
|
|
179
179
|
? analyticsLoginPasswordSecretRefRaw
|
|
180
180
|
: "password";
|
|
181
|
+
const sqlitePathRaw = simpleConfig.storage?.sqlite_path ?? process.env.SQLITE_PATH;
|
|
182
|
+
const sqlitePath = typeof sqlitePathRaw === "string" && sqlitePathRaw.trim().length > 0 ? sqlitePathRaw.trim() : ".apphud-mcp/apphud.db";
|
|
183
|
+
const etlStorageDirRaw = simpleConfig.etl?.storage_dir ?? process.env.ETL_STORAGE_DIR;
|
|
184
|
+
const etlStorageDir = typeof etlStorageDirRaw === "string" && etlStorageDirRaw.trim().length > 0
|
|
185
|
+
? etlStorageDirRaw.trim()
|
|
186
|
+
: ".apphud-etl";
|
|
187
|
+
const etlIncomingDirRaw = simpleConfig.etl?.incoming_dir ?? process.env.ETL_INCOMING_DIR;
|
|
188
|
+
const etlIncomingDir = typeof etlIncomingDirRaw === "string" && etlIncomingDirRaw.trim().length > 0
|
|
189
|
+
? etlIncomingDirRaw.trim()
|
|
190
|
+
: `${etlStorageDir}/incoming`;
|
|
191
|
+
const etlTenantIdRaw = simpleConfig.etl?.tenant_id ?? process.env.ETL_TENANT_ID;
|
|
192
|
+
const etlTenantId = typeof etlTenantIdRaw === "string" && etlTenantIdRaw.trim().length > 0 ? etlTenantIdRaw.trim() : "default";
|
|
193
|
+
const etlExportsApiBaseUrlRaw = simpleConfig.apphud?.etl_exports_api_base_url ?? process.env.APPHUD_ETL_EXPORTS_API_BASE_URL;
|
|
194
|
+
const apphudEtlExportsApiBaseUrl = typeof etlExportsApiBaseUrlRaw === "string" && etlExportsApiBaseUrlRaw.trim().length > 0
|
|
195
|
+
? etlExportsApiBaseUrlRaw.trim()
|
|
196
|
+
: "https://api.apphud.com/v1";
|
|
197
|
+
const apphudEtlExportsListPathRaw = simpleConfig.apphud?.etl_exports_list_path ?? process.env.APPHUD_ETL_EXPORTS_LIST_PATH;
|
|
198
|
+
const apphudEtlExportsListPath = typeof apphudEtlExportsListPathRaw === "string" && apphudEtlExportsListPathRaw.trim().length > 0
|
|
199
|
+
? apphudEtlExportsListPathRaw.trim()
|
|
200
|
+
: "/apps/{app_id}/etl-exports";
|
|
201
|
+
const apphudEtlExportsDownloadPathRaw = simpleConfig.apphud?.etl_exports_download_path ?? process.env.APPHUD_ETL_EXPORTS_DOWNLOAD_PATH;
|
|
202
|
+
const apphudEtlExportsDownloadPath = typeof apphudEtlExportsDownloadPathRaw === "string" && apphudEtlExportsDownloadPathRaw.trim().length > 0
|
|
203
|
+
? apphudEtlExportsDownloadPathRaw.trim()
|
|
204
|
+
: "/apps/{app_id}/etl-exports/{export_id}";
|
|
181
205
|
return {
|
|
182
206
|
nodeEnv: simpleConfig.node_env ?? process.env.NODE_ENV ?? "development",
|
|
183
207
|
port: parseNumber(simpleConfig.transport?.port ?? process.env.PORT, 8080),
|
|
184
208
|
databaseUrl,
|
|
185
209
|
storageBackend,
|
|
210
|
+
sqlitePath,
|
|
186
211
|
bootstrapApps,
|
|
187
212
|
defaultTenantId,
|
|
188
213
|
defaultRole,
|
|
@@ -209,7 +234,17 @@ export function loadEnvConfig(options = {}) {
|
|
|
209
234
|
"/sessions",
|
|
210
235
|
apphudAnalyticsLoginEmailSecretRef: analyticsLoginEmailSecretRef,
|
|
211
236
|
apphudAnalyticsLoginPasswordSecretRef: analyticsLoginPasswordSecretRef,
|
|
237
|
+
apphudEtlExportsApiBaseUrl,
|
|
238
|
+
apphudEtlExportsListPath,
|
|
239
|
+
apphudEtlExportsDownloadPath,
|
|
212
240
|
apphudWebhookTokenHeader: simpleConfig.security?.webhook_token_header ?? process.env.APPHUD_WEBHOOK_TOKEN_HEADER ?? "x-apphud-token",
|
|
241
|
+
etlEnabled: parseBoolean(simpleConfig.etl?.enabled ?? process.env.ETL_ENABLED, false),
|
|
242
|
+
etlTenantId,
|
|
243
|
+
etlPollIntervalMinutes: parseNumber(simpleConfig.etl?.poll_interval_minutes ?? process.env.ETL_POLL_INTERVAL_MINUTES, 60),
|
|
244
|
+
etlStorageDir,
|
|
245
|
+
etlIncomingDir,
|
|
246
|
+
etlAlertsStaleHours: parseNumber(simpleConfig.etl?.alerts_stale_hours ?? process.env.ETL_ALERTS_STALE_HOURS, 30),
|
|
247
|
+
etlRemoteFetchEnabled: parseBoolean(simpleConfig.etl?.remote_fetch_enabled ?? process.env.ETL_REMOTE_FETCH_ENABLED, false),
|
|
213
248
|
httpEnabled: parseBoolean(simpleConfig.transport?.http_enabled ?? process.env.HTTP_ENABLED, false),
|
|
214
249
|
mcpStdioEnabled: parseBoolean(simpleConfig.transport?.mcp_stdio_enabled ?? process.env.MCP_STDIO_ENABLED, true),
|
|
215
250
|
};
|
|
@@ -217,7 +252,17 @@ export function loadEnvConfig(options = {}) {
|
|
|
217
252
|
export function buildExampleSimpleConfig() {
|
|
218
253
|
return {
|
|
219
254
|
storage: {
|
|
220
|
-
backend: "
|
|
255
|
+
backend: "sqlite",
|
|
256
|
+
sqlite_path: ".apphud-mcp/apphud.db",
|
|
257
|
+
},
|
|
258
|
+
etl: {
|
|
259
|
+
enabled: false,
|
|
260
|
+
tenant_id: "default",
|
|
261
|
+
poll_interval_minutes: 60,
|
|
262
|
+
storage_dir: ".apphud-etl",
|
|
263
|
+
incoming_dir: ".apphud-etl/incoming",
|
|
264
|
+
alerts_stale_hours: 30,
|
|
265
|
+
remote_fetch_enabled: false,
|
|
221
266
|
},
|
|
222
267
|
defaults: {
|
|
223
268
|
tenant_id: "tenant_default",
|
|
@@ -244,6 +289,9 @@ export function buildExampleSimpleConfig() {
|
|
|
244
289
|
analytics_login_path: "/sessions",
|
|
245
290
|
analytics_login_email_secret_ref: "login",
|
|
246
291
|
analytics_login_password_secret_ref: "password",
|
|
292
|
+
etl_exports_api_base_url: "https://api.apphud.com/v1",
|
|
293
|
+
etl_exports_list_path: "/apps/{app_id}/etl-exports",
|
|
294
|
+
etl_exports_download_path: "/apps/{app_id}/etl-exports/{export_id}",
|
|
247
295
|
},
|
|
248
296
|
};
|
|
249
297
|
}
|
|
@@ -1,51 +1,99 @@
|
|
|
1
1
|
export const TOOL_PERMISSIONS = {
|
|
2
2
|
analyst: new Set([
|
|
3
3
|
"apphud_apps_list",
|
|
4
|
+
"apphud_apps_list_local",
|
|
5
|
+
"apphud_dashboard_local",
|
|
4
6
|
"apphud_analytics_events_list",
|
|
7
|
+
"apphud_analytics_events_list_local",
|
|
5
8
|
"apphud_analytics_active_subscriptions",
|
|
9
|
+
"apphud_analytics_active_subscriptions_local",
|
|
6
10
|
"apphud_analytics_capabilities_get",
|
|
11
|
+
"apphud_analytics_capabilities_get_local",
|
|
7
12
|
"apphud_analytics_metrics_list",
|
|
13
|
+
"apphud_analytics_metrics_list_local",
|
|
8
14
|
"apphud_analytics_metric_value",
|
|
15
|
+
"apphud_analytics_metric_value_local",
|
|
9
16
|
"apphud_analytics_metric_timeseries",
|
|
17
|
+
"apphud_analytics_metric_timeseries_local",
|
|
10
18
|
"apphud_analytics_metric_breakdown",
|
|
19
|
+
"apphud_analytics_metric_breakdown_local",
|
|
11
20
|
"apphud_analytics_revenue_summary",
|
|
21
|
+
"apphud_analytics_revenue_summary_local",
|
|
12
22
|
"apphud_analytics_subscriptions_summary",
|
|
23
|
+
"apphud_analytics_subscriptions_summary_local",
|
|
13
24
|
"apphud_analytics_conversion_trial_to_paid",
|
|
25
|
+
"apphud_analytics_conversion_trial_to_paid_local",
|
|
14
26
|
"apphud_analytics_cohorts_retention",
|
|
27
|
+
"apphud_analytics_cohorts_retention_local",
|
|
15
28
|
"apphud_analytics_cohorts_ltv",
|
|
29
|
+
"apphud_analytics_cohorts_ltv_local",
|
|
16
30
|
"apphud_analytics_query_raw",
|
|
31
|
+
"apphud_analytics_query_raw_local",
|
|
32
|
+
"apphud_etl_local_status",
|
|
17
33
|
]),
|
|
18
34
|
support: new Set([
|
|
19
35
|
"apphud_apps_list",
|
|
36
|
+
"apphud_apps_list_local",
|
|
37
|
+
"apphud_dashboard_local",
|
|
20
38
|
"apphud_analytics_events_list",
|
|
39
|
+
"apphud_analytics_events_list_local",
|
|
21
40
|
"apphud_analytics_active_subscriptions",
|
|
41
|
+
"apphud_analytics_active_subscriptions_local",
|
|
22
42
|
"apphud_analytics_capabilities_get",
|
|
43
|
+
"apphud_analytics_capabilities_get_local",
|
|
23
44
|
"apphud_analytics_metrics_list",
|
|
45
|
+
"apphud_analytics_metrics_list_local",
|
|
24
46
|
"apphud_analytics_metric_value",
|
|
47
|
+
"apphud_analytics_metric_value_local",
|
|
25
48
|
"apphud_analytics_metric_timeseries",
|
|
49
|
+
"apphud_analytics_metric_timeseries_local",
|
|
26
50
|
"apphud_analytics_metric_breakdown",
|
|
51
|
+
"apphud_analytics_metric_breakdown_local",
|
|
27
52
|
"apphud_analytics_revenue_summary",
|
|
53
|
+
"apphud_analytics_revenue_summary_local",
|
|
28
54
|
"apphud_analytics_subscriptions_summary",
|
|
55
|
+
"apphud_analytics_subscriptions_summary_local",
|
|
29
56
|
"apphud_analytics_conversion_trial_to_paid",
|
|
57
|
+
"apphud_analytics_conversion_trial_to_paid_local",
|
|
30
58
|
"apphud_analytics_cohorts_retention",
|
|
59
|
+
"apphud_analytics_cohorts_retention_local",
|
|
31
60
|
"apphud_analytics_cohorts_ltv",
|
|
61
|
+
"apphud_analytics_cohorts_ltv_local",
|
|
32
62
|
"apphud_analytics_query_raw",
|
|
63
|
+
"apphud_analytics_query_raw_local",
|
|
64
|
+
"apphud_etl_local_status",
|
|
33
65
|
]),
|
|
34
66
|
admin: new Set([
|
|
35
67
|
"apphud_apps_list",
|
|
68
|
+
"apphud_apps_list_local",
|
|
69
|
+
"apphud_dashboard_local",
|
|
36
70
|
"apphud_analytics_events_list",
|
|
71
|
+
"apphud_analytics_events_list_local",
|
|
37
72
|
"apphud_analytics_active_subscriptions",
|
|
73
|
+
"apphud_analytics_active_subscriptions_local",
|
|
38
74
|
"apphud_analytics_capabilities_get",
|
|
75
|
+
"apphud_analytics_capabilities_get_local",
|
|
39
76
|
"apphud_analytics_metrics_list",
|
|
77
|
+
"apphud_analytics_metrics_list_local",
|
|
40
78
|
"apphud_analytics_metric_value",
|
|
79
|
+
"apphud_analytics_metric_value_local",
|
|
41
80
|
"apphud_analytics_metric_timeseries",
|
|
81
|
+
"apphud_analytics_metric_timeseries_local",
|
|
42
82
|
"apphud_analytics_metric_breakdown",
|
|
83
|
+
"apphud_analytics_metric_breakdown_local",
|
|
43
84
|
"apphud_analytics_revenue_summary",
|
|
85
|
+
"apphud_analytics_revenue_summary_local",
|
|
44
86
|
"apphud_analytics_subscriptions_summary",
|
|
87
|
+
"apphud_analytics_subscriptions_summary_local",
|
|
45
88
|
"apphud_analytics_conversion_trial_to_paid",
|
|
89
|
+
"apphud_analytics_conversion_trial_to_paid_local",
|
|
46
90
|
"apphud_analytics_cohorts_retention",
|
|
91
|
+
"apphud_analytics_cohorts_retention_local",
|
|
47
92
|
"apphud_analytics_cohorts_ltv",
|
|
93
|
+
"apphud_analytics_cohorts_ltv_local",
|
|
48
94
|
"apphud_analytics_query_raw",
|
|
95
|
+
"apphud_analytics_query_raw_local",
|
|
96
|
+
"apphud_etl_local_status",
|
|
49
97
|
]),
|
|
50
98
|
};
|
|
51
99
|
export const METRIC_REVENUE_EVENT_TYPES = [
|