apphud-mcp 0.2.2 → 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 +78 -50
- package/dist/src/cli.js +16 -10
- package/dist/src/config/env.js +20 -20
- 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 +657 -122
- package/dist/src/services/apphudClient.js +225 -26
- package/dist/src/services/etlService.js +351 -436
- package/dist/src/tools/remoteTools.js +398 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
# apphud-mcp <img src="./assets/apphud-mcp-logo.svg" alt="apphud + mcp" height="28" />
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for Apphud dashboard analytics endpoints (apps, events, metrics, cohorts).
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
1.
|
|
7
|
+
1. Add MCP server:
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
10
|
{
|
|
11
11
|
"mcpServers": {
|
|
12
12
|
"apphud-mcp": {
|
|
13
|
-
"command": "
|
|
14
|
-
"args": [
|
|
15
|
-
"/Users/you/apphud-mcp/dist/src/cli.js",
|
|
16
|
-
"start"
|
|
17
|
-
],
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "apphud-mcp@0.2.2", "start"],
|
|
18
15
|
"env": {
|
|
19
16
|
"login": "your@apphud.email",
|
|
20
17
|
"password": "your_apphud_password"
|
|
@@ -24,56 +21,87 @@ Zero-config MCP server for Apphud Dashboard Analytics with local SQLite storage.
|
|
|
24
21
|
}
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
2. Restart MCP server
|
|
24
|
+
2. Restart MCP server.
|
|
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`)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
- `apphud_etl_local_status`
|
|
31
|
-
- `apphud_apps_list_local`
|
|
32
|
-
- `apphud_dashboard_local`
|
|
30
|
+
## HTTP Tool Calls
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
If `HTTP_ENABLED=true`, you can call tools over HTTP (stable payload path for web clients):
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
2. `apphud_analytics_capabilities_get` (with `app_id` from step 1)
|
|
50
|
-
3. `apphud_analytics_revenue_summary` (for a date range)
|
|
51
|
-
|
|
52
|
-
## Available Tools
|
|
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
|
+
```
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
- `
|
|
48
|
+
The endpoint also accepts `toolName` and stringified `arguments` JSON for compatibility with buggy wrappers.
|
|
49
|
+
|
|
50
|
+
## ETL Setup
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary>Google Cloud Storage (GCS) setup</summary>
|
|
54
|
+
|
|
55
|
+
1. In Apphud:
|
|
56
|
+
- Open `Integrations` -> `GCS ETL` -> `Add connection`.
|
|
57
|
+
- Enter project/service account/bucket and enable export for apps.
|
|
58
|
+
2. In Google Cloud Console:
|
|
59
|
+
- Create/select a bucket in [Cloud Storage](https://console.cloud.google.com/storage/browser).
|
|
60
|
+
- Create a Service Account in [IAM](https://console.cloud.google.com/iam-admin/serviceaccounts).
|
|
61
|
+
- Create JSON key for that service account.
|
|
62
|
+
- Grant access to the bucket (read for MCP side, write for Apphud side as needed by your policy).
|
|
63
|
+
3. MCP env vars:
|
|
64
|
+
- `ETL_SOURCE=gcs`
|
|
65
|
+
- `ETL_GCS_BUCKET=<bucket-name>`
|
|
66
|
+
- `ETL_GCS_PREFIX=<optional-prefix>`
|
|
67
|
+
- `GOOGLE_APPLICATION_CREDENTIALS=<path-to-service-account-json>`
|
|
68
|
+
4. Ensure `gsutil` is installed and authenticated in runtime environment.
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary>Amazon S3 setup</summary>
|
|
74
|
+
|
|
75
|
+
1. In Apphud:
|
|
76
|
+
- Open `Integrations` -> `S3 ETL` -> `Add connection`.
|
|
77
|
+
- Fill bucket/credentials/region and enable export for apps.
|
|
78
|
+
2. In AWS Console:
|
|
79
|
+
- Create/select bucket in [S3](https://s3.console.aws.amazon.com/s3/home).
|
|
80
|
+
- Create IAM user/role with bucket access.
|
|
81
|
+
- Generate access key (if using user credentials).
|
|
82
|
+
3. MCP env vars:
|
|
83
|
+
- `ETL_SOURCE=s3`
|
|
84
|
+
- `ETL_S3_BUCKET=<bucket-name>`
|
|
85
|
+
- `ETL_S3_PREFIX=<optional-prefix>`
|
|
86
|
+
- `AWS_ACCESS_KEY_ID=<key-id>`
|
|
87
|
+
- `AWS_SECRET_ACCESS_KEY=<secret>`
|
|
88
|
+
- `AWS_REGION=<region>`
|
|
89
|
+
4. Ensure AWS CLI (`aws`) is installed and usable in runtime environment.
|
|
90
|
+
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
## Defaults
|
|
94
|
+
|
|
95
|
+
- SQLite DB: `.apphud-mcp/apphud.db`
|
|
96
|
+
- Incoming ETL files: `.apphud-etl/incoming`
|
|
97
|
+
- Poll interval: 60 minutes
|
|
98
|
+
- ETL enabled: `true`
|
|
99
|
+
- ETL source default: `none` (set `ETL_SOURCE=gcs` or `ETL_SOURCE=s3`)
|
|
100
|
+
|
|
101
|
+
## Useful Tools
|
|
71
102
|
|
|
72
|
-
Remote tools (still available):
|
|
73
103
|
- `apphud_apps_list`
|
|
74
104
|
- `apphud_analytics_events_list`
|
|
75
|
-
- `apphud_analytics_active_subscriptions`
|
|
76
|
-
- `apphud_analytics_capabilities_get`
|
|
77
105
|
- `apphud_analytics_metrics_list`
|
|
78
106
|
- `apphud_analytics_metric_value`
|
|
79
107
|
- `apphud_analytics_metric_timeseries`
|
package/dist/src/cli.js
CHANGED
|
@@ -34,26 +34,32 @@ Usage:
|
|
|
34
34
|
apphud-mcp start [--config <path>] [--config-json '<json>']
|
|
35
35
|
apphud-mcp init-config [--out <path>] [--force]
|
|
36
36
|
|
|
37
|
-
Zero-config quick start
|
|
37
|
+
Zero-config quick start:
|
|
38
38
|
1) Add MCP server using:
|
|
39
|
-
npx -y apphud-mcp@0.2.
|
|
39
|
+
npx -y apphud-mcp@0.2.2 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
|
|
50
50
|
- ETL storage: .apphud-etl
|
|
51
51
|
- Poll interval: 60 minutes
|
|
52
|
-
-
|
|
52
|
+
- ETL worker enabled; source is none until you set GCS/S3
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
Storage source setup:
|
|
55
|
+
GCS:
|
|
56
|
+
ETL_SOURCE=gcs
|
|
57
|
+
ETL_GCS_BUCKET=<bucket>
|
|
58
|
+
ETL_GCS_PREFIX=<prefix optional>
|
|
59
|
+
S3:
|
|
60
|
+
ETL_SOURCE=s3
|
|
61
|
+
ETL_S3_BUCKET=<bucket>
|
|
62
|
+
ETL_S3_PREFIX=<prefix optional>
|
|
57
63
|
`);
|
|
58
64
|
}
|
|
59
65
|
async function ensureWritable(filePath, force = false) {
|
package/dist/src/config/env.js
CHANGED
|
@@ -190,18 +190,16 @@ export function loadEnvConfig(options = {}) {
|
|
|
190
190
|
: `${etlStorageDir}/incoming`;
|
|
191
191
|
const etlTenantIdRaw = simpleConfig.etl?.tenant_id ?? process.env.ETL_TENANT_ID;
|
|
192
192
|
const etlTenantId = typeof etlTenantIdRaw === "string" && etlTenantIdRaw.trim().length > 0 ? etlTenantIdRaw.trim() : "default";
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
? apphudEtlExportsDownloadPathRaw.trim()
|
|
204
|
-
: "/apps/{app_id}/etl-exports/{export_id}";
|
|
193
|
+
const etlSourceRaw = simpleConfig.etl?.source ?? process.env.ETL_SOURCE;
|
|
194
|
+
const etlSource = etlSourceRaw === "gcs" || etlSourceRaw === "s3" || etlSourceRaw === "none" ? etlSourceRaw : "none";
|
|
195
|
+
const etlGcsBucketRaw = simpleConfig.etl?.gcs_bucket ?? process.env.ETL_GCS_BUCKET;
|
|
196
|
+
const etlGcsBucket = typeof etlGcsBucketRaw === "string" && etlGcsBucketRaw.trim().length > 0 ? etlGcsBucketRaw.trim() : undefined;
|
|
197
|
+
const etlGcsPrefixRaw = simpleConfig.etl?.gcs_prefix ?? process.env.ETL_GCS_PREFIX;
|
|
198
|
+
const etlGcsPrefix = typeof etlGcsPrefixRaw === "string" && etlGcsPrefixRaw.trim().length > 0 ? etlGcsPrefixRaw.trim() : undefined;
|
|
199
|
+
const etlS3BucketRaw = simpleConfig.etl?.s3_bucket ?? process.env.ETL_S3_BUCKET;
|
|
200
|
+
const etlS3Bucket = typeof etlS3BucketRaw === "string" && etlS3BucketRaw.trim().length > 0 ? etlS3BucketRaw.trim() : undefined;
|
|
201
|
+
const etlS3PrefixRaw = simpleConfig.etl?.s3_prefix ?? process.env.ETL_S3_PREFIX;
|
|
202
|
+
const etlS3Prefix = typeof etlS3PrefixRaw === "string" && etlS3PrefixRaw.trim().length > 0 ? etlS3PrefixRaw.trim() : undefined;
|
|
205
203
|
return {
|
|
206
204
|
nodeEnv: simpleConfig.node_env ?? process.env.NODE_ENV ?? "development",
|
|
207
205
|
port: parseNumber(simpleConfig.transport?.port ?? process.env.PORT, 8080),
|
|
@@ -234,9 +232,6 @@ export function loadEnvConfig(options = {}) {
|
|
|
234
232
|
"/sessions",
|
|
235
233
|
apphudAnalyticsLoginEmailSecretRef: analyticsLoginEmailSecretRef,
|
|
236
234
|
apphudAnalyticsLoginPasswordSecretRef: analyticsLoginPasswordSecretRef,
|
|
237
|
-
apphudEtlExportsApiBaseUrl,
|
|
238
|
-
apphudEtlExportsListPath,
|
|
239
|
-
apphudEtlExportsDownloadPath,
|
|
240
235
|
apphudWebhookTokenHeader: simpleConfig.security?.webhook_token_header ?? process.env.APPHUD_WEBHOOK_TOKEN_HEADER ?? "x-apphud-token",
|
|
241
236
|
etlEnabled: parseBoolean(simpleConfig.etl?.enabled ?? process.env.ETL_ENABLED, true),
|
|
242
237
|
etlTenantId,
|
|
@@ -244,7 +239,11 @@ export function loadEnvConfig(options = {}) {
|
|
|
244
239
|
etlStorageDir,
|
|
245
240
|
etlIncomingDir,
|
|
246
241
|
etlAlertsStaleHours: parseNumber(simpleConfig.etl?.alerts_stale_hours ?? process.env.ETL_ALERTS_STALE_HOURS, 30),
|
|
247
|
-
|
|
242
|
+
etlSource,
|
|
243
|
+
etlGcsBucket,
|
|
244
|
+
etlGcsPrefix,
|
|
245
|
+
etlS3Bucket,
|
|
246
|
+
etlS3Prefix,
|
|
248
247
|
httpEnabled: parseBoolean(simpleConfig.transport?.http_enabled ?? process.env.HTTP_ENABLED, false),
|
|
249
248
|
mcpStdioEnabled: parseBoolean(simpleConfig.transport?.mcp_stdio_enabled ?? process.env.MCP_STDIO_ENABLED, true),
|
|
250
249
|
};
|
|
@@ -262,7 +261,11 @@ export function buildExampleSimpleConfig() {
|
|
|
262
261
|
storage_dir: ".apphud-etl",
|
|
263
262
|
incoming_dir: ".apphud-etl/incoming",
|
|
264
263
|
alerts_stale_hours: 30,
|
|
265
|
-
|
|
264
|
+
source: "none",
|
|
265
|
+
gcs_bucket: "your-gcs-bucket",
|
|
266
|
+
gcs_prefix: "apphud/exports",
|
|
267
|
+
s3_bucket: "your-s3-bucket",
|
|
268
|
+
s3_prefix: "apphud/exports",
|
|
266
269
|
},
|
|
267
270
|
defaults: {
|
|
268
271
|
tenant_id: "tenant_default",
|
|
@@ -289,9 +292,6 @@ export function buildExampleSimpleConfig() {
|
|
|
289
292
|
analytics_login_path: "/sessions",
|
|
290
293
|
analytics_login_email_secret_ref: "login",
|
|
291
294
|
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}",
|
|
295
295
|
},
|
|
296
296
|
};
|
|
297
297
|
}
|
|
@@ -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;
|