apphud-mcp 0.2.5 → 0.2.6

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 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 dashboard analytics endpoints (apps, events, metrics, cohorts).
3
+ MCP server for Apphud dashboard analytics endpoints and Product Hub mutations (paywalls, placements, items).
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -101,6 +101,12 @@ The endpoint also accepts `toolName` and stringified `arguments` JSON for compat
101
101
  ## Useful Tools
102
102
 
103
103
  - `apphud_apps_list`
104
+ - `apphud_paywall_item_add`
105
+ - `apphud_placement_item_set_enabled`
106
+ - `apphud_paywall_item_delete`
107
+ - `apphud_placement_item_delete`
108
+ - `apphud_placement_delete`
109
+ - `apphud_paywall_config_delete`
104
110
  - `apphud_analytics_events_list`
105
111
  - `apphud_analytics_metrics_list`
106
112
  - `apphud_analytics_metric_value`
@@ -14,10 +14,18 @@ const ANALYTICS_TOOL_PERMISSIONS = [
14
14
  "apphud_analytics_cohorts_ltv",
15
15
  "apphud_analytics_query_raw",
16
16
  ];
17
+ const PRODUCT_HUB_MUTATION_TOOL_PERMISSIONS = [
18
+ "apphud_paywall_item_add",
19
+ "apphud_placement_item_set_enabled",
20
+ "apphud_paywall_item_delete",
21
+ "apphud_placement_item_delete",
22
+ "apphud_placement_delete",
23
+ "apphud_paywall_config_delete",
24
+ ];
17
25
  export const TOOL_PERMISSIONS = {
18
26
  analyst: new Set(ANALYTICS_TOOL_PERMISSIONS),
19
27
  support: new Set(ANALYTICS_TOOL_PERMISSIONS),
20
- admin: new Set(ANALYTICS_TOOL_PERMISSIONS),
28
+ admin: new Set([...ANALYTICS_TOOL_PERMISSIONS, ...PRODUCT_HUB_MUTATION_TOOL_PERMISSIONS]),
21
29
  };
22
30
  export const METRIC_REVENUE_EVENT_TYPES = [
23
31
  "trial_converted",
@@ -60,7 +60,7 @@ export function createHttpServer(container) {
60
60
  app.get("/status", async (_req, res) => {
61
61
  res.status(200).json({
62
62
  status: "ok",
63
- mode: "analytics_only",
63
+ mode: "analytics_and_product_hub",
64
64
  node_env: container.config.nodeEnv,
65
65
  timestamp: new Date().toISOString(),
66
66
  });
@@ -11,7 +11,7 @@ function jsonResult(payload, isError = false) {
11
11
  export function createMcpServer(container) {
12
12
  const server = new McpServer({
13
13
  name: "apphud-mcp",
14
- version: "0.2.4",
14
+ version: "0.2.6",
15
15
  });
16
16
  for (const [toolName, definition] of getRemoteToolEntries()) {
17
17
  server.tool(toolName, definition.description, definition.inputShape, async (input) => {
@@ -975,6 +975,131 @@ export class AnalyticsService {
975
975
  response: this.selectRawPayload(auth, true, raw.payload),
976
976
  };
977
977
  }
978
+ async paywallItemAdd(auth, input) {
979
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
980
+ this.assertRawAccess(auth, input.include_raw);
981
+ const created = await this.apphudClient.createPaywallItem(app, {
982
+ apphudAppId: input.apphud_app_id,
983
+ paywallConfigId: input.paywall_config_id,
984
+ productBundleId: input.product_bundle_id,
985
+ });
986
+ return {
987
+ app_id: app.appId,
988
+ apphud_app_id: created.apphudAppId,
989
+ paywall_config_id: created.paywallConfigId,
990
+ product_bundle_id: input.product_bundle_id,
991
+ source: "apphud_dashboard_api",
992
+ source_used: created.sourcePath,
993
+ status: created.status,
994
+ item: created.item,
995
+ created: true,
996
+ retrieved_at: new Date().toISOString(),
997
+ raw_payload: this.selectRawPayload(auth, input.include_raw, created.payload),
998
+ };
999
+ }
1000
+ async placementItemSetEnabled(auth, input) {
1001
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
1002
+ this.assertRawAccess(auth, input.include_raw);
1003
+ const updated = await this.apphudClient.setPlacementItemEnabled(app, {
1004
+ apphudAppId: input.apphud_app_id,
1005
+ placementId: input.placement_id,
1006
+ placementItemId: input.placement_item_id,
1007
+ paywallConfigId: input.paywall_config_id,
1008
+ enabled: input.enabled,
1009
+ });
1010
+ return {
1011
+ app_id: app.appId,
1012
+ apphud_app_id: updated.apphudAppId,
1013
+ placement_id: updated.placementId,
1014
+ placement_item_id: updated.placementItemId,
1015
+ enabled: input.enabled,
1016
+ state: input.enabled ? "active" : "disabled",
1017
+ source: "apphud_dashboard_api",
1018
+ source_used: updated.sourcePath,
1019
+ status: updated.status,
1020
+ item: updated.item,
1021
+ updated: true,
1022
+ retrieved_at: new Date().toISOString(),
1023
+ raw_payload: this.selectRawPayload(auth, input.include_raw, updated.payload),
1024
+ };
1025
+ }
1026
+ async paywallItemDelete(auth, input) {
1027
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
1028
+ this.assertRawAccess(auth, input.include_raw);
1029
+ const result = await this.apphudClient.deletePaywallItem(app, {
1030
+ paywallItemId: input.paywall_item_id,
1031
+ });
1032
+ return {
1033
+ app_id: app.appId,
1034
+ apphud_app_id: input.apphud_app_id ?? app.appId,
1035
+ paywall_item_id: result.paywallItemId,
1036
+ deleted: true,
1037
+ source: "apphud_dashboard_api",
1038
+ source_used: result.sourcePath,
1039
+ status: result.status,
1040
+ retrieved_at: new Date().toISOString(),
1041
+ raw_payload: this.selectRawPayload(auth, input.include_raw, result.payload),
1042
+ };
1043
+ }
1044
+ async placementItemDelete(auth, input) {
1045
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
1046
+ this.assertRawAccess(auth, input.include_raw);
1047
+ const result = await this.apphudClient.deletePlacementItem(app, {
1048
+ apphudAppId: input.apphud_app_id,
1049
+ placementId: input.placement_id,
1050
+ placementItemId: input.placement_item_id,
1051
+ });
1052
+ return {
1053
+ app_id: app.appId,
1054
+ apphud_app_id: result.apphudAppId,
1055
+ placement_id: result.placementId,
1056
+ placement_item_id: result.placementItemId,
1057
+ deleted: true,
1058
+ source: "apphud_dashboard_api",
1059
+ source_used: result.sourcePath,
1060
+ status: result.status,
1061
+ retrieved_at: new Date().toISOString(),
1062
+ raw_payload: this.selectRawPayload(auth, input.include_raw, result.payload),
1063
+ };
1064
+ }
1065
+ async placementDelete(auth, input) {
1066
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
1067
+ this.assertRawAccess(auth, input.include_raw);
1068
+ const result = await this.apphudClient.deletePlacement(app, {
1069
+ apphudAppId: input.apphud_app_id,
1070
+ placementId: input.placement_id,
1071
+ });
1072
+ return {
1073
+ app_id: app.appId,
1074
+ apphud_app_id: result.apphudAppId,
1075
+ placement_id: result.placementId,
1076
+ deleted: true,
1077
+ source: "apphud_dashboard_api",
1078
+ source_used: result.sourcePath,
1079
+ status: result.status,
1080
+ retrieved_at: new Date().toISOString(),
1081
+ raw_payload: this.selectRawPayload(auth, input.include_raw, result.payload),
1082
+ };
1083
+ }
1084
+ async paywallConfigDelete(auth, input) {
1085
+ const app = await this.resolveApp(auth, input.app_id, input.apphud_app_id);
1086
+ this.assertRawAccess(auth, input.include_raw);
1087
+ const result = await this.apphudClient.deletePaywallConfig(app, {
1088
+ apphudAppId: input.apphud_app_id,
1089
+ paywallConfigId: input.paywall_config_id,
1090
+ });
1091
+ return {
1092
+ app_id: app.appId,
1093
+ apphud_app_id: result.apphudAppId,
1094
+ paywall_config_id: result.paywallConfigId,
1095
+ deleted: true,
1096
+ source: "apphud_dashboard_api",
1097
+ source_used: result.sourcePath,
1098
+ status: result.status,
1099
+ retrieved_at: new Date().toISOString(),
1100
+ raw_payload: this.selectRawPayload(auth, input.include_raw, result.payload),
1101
+ };
1102
+ }
978
1103
  async resolveApp(auth, appId, apphudAppId) {
979
1104
  const explicitAppId = appId?.trim() || apphudAppId?.trim();
980
1105
  if (explicitAppId) {
@@ -122,6 +122,31 @@ function asArray(value) {
122
122
  }
123
123
  return value.filter((item) => !!item && typeof item === "object");
124
124
  }
125
+ function extractFirstRecord(value) {
126
+ const root = asRecord(value);
127
+ const data = asRecord(root.data);
128
+ const directCandidates = [data.results, data.result, root.results, root.result];
129
+ for (const candidate of directCandidates) {
130
+ const record = asRecord(candidate);
131
+ if (Object.keys(record).length > 0) {
132
+ return record;
133
+ }
134
+ }
135
+ const listCandidates = [data.results, data.items, root.results, root.items];
136
+ for (const candidate of listCandidates) {
137
+ const rows = asArray(candidate);
138
+ if (rows.length > 0) {
139
+ return rows[0];
140
+ }
141
+ }
142
+ if (Object.keys(data).length > 0) {
143
+ return data;
144
+ }
145
+ if (Object.keys(root).length > 0) {
146
+ return root;
147
+ }
148
+ return undefined;
149
+ }
125
150
  function readStringValue(value) {
126
151
  if (typeof value === "string" && value.trim().length > 0) {
127
152
  return value.trim();
@@ -1787,6 +1812,219 @@ export class ApphudClient {
1787
1812
  status: response.status,
1788
1813
  };
1789
1814
  }
1815
+ async createPaywallItem(app, options) {
1816
+ const apphudAppId = options.apphudAppId ?? app.appId;
1817
+ const paywallConfigId = encodeURIComponent(options.paywallConfigId);
1818
+ const encodedAppId = encodeURIComponent(apphudAppId);
1819
+ const body = {
1820
+ product_bundle_id: options.productBundleId,
1821
+ };
1822
+ const result = await this.requestAnalyticsWithFallback(app, [
1823
+ {
1824
+ path: `/paywall_configs/${paywallConfigId}/paywall_items`,
1825
+ method: "POST",
1826
+ body,
1827
+ },
1828
+ {
1829
+ path: `/api/v1/paywall_configs/${paywallConfigId}/paywall_items`,
1830
+ method: "POST",
1831
+ body,
1832
+ },
1833
+ {
1834
+ path: `/apps/${encodedAppId}/paywall_configs/${paywallConfigId}/paywall_items`,
1835
+ method: "POST",
1836
+ body,
1837
+ },
1838
+ ], "create paywall item");
1839
+ return {
1840
+ apphudAppId,
1841
+ paywallConfigId: options.paywallConfigId,
1842
+ sourcePath: result.sourcePath,
1843
+ status: result.status,
1844
+ payload: result.payload,
1845
+ item: extractFirstRecord(result.payload),
1846
+ };
1847
+ }
1848
+ async setPlacementItemEnabled(app, options) {
1849
+ const apphudAppId = options.apphudAppId ?? app.appId;
1850
+ const encodedAppId = encodeURIComponent(apphudAppId);
1851
+ const placementId = encodeURIComponent(options.placementId);
1852
+ const placementItemId = encodeURIComponent(options.placementItemId);
1853
+ const body = {
1854
+ active: options.enabled,
1855
+ };
1856
+ if (options.paywallConfigId && options.paywallConfigId.trim().length > 0) {
1857
+ body.paywall_config_id = options.paywallConfigId;
1858
+ }
1859
+ const result = await this.requestAnalyticsWithFallback(app, [
1860
+ {
1861
+ path: `/apps/${encodedAppId}/placements/${placementId}/items/${placementItemId}`,
1862
+ method: "PUT",
1863
+ body,
1864
+ },
1865
+ {
1866
+ path: `/api/v1/apps/${encodedAppId}/placements/${placementId}/items/${placementItemId}`,
1867
+ method: "PUT",
1868
+ body,
1869
+ },
1870
+ {
1871
+ path: `/placements/${placementId}/items/${placementItemId}`,
1872
+ method: "PUT",
1873
+ body,
1874
+ },
1875
+ ], "update placement item state");
1876
+ return {
1877
+ apphudAppId,
1878
+ placementId: options.placementId,
1879
+ placementItemId: options.placementItemId,
1880
+ sourcePath: result.sourcePath,
1881
+ status: result.status,
1882
+ payload: result.payload,
1883
+ item: extractFirstRecord(result.payload),
1884
+ };
1885
+ }
1886
+ async deletePaywallItem(app, options) {
1887
+ const paywallItemId = encodeURIComponent(options.paywallItemId);
1888
+ const result = await this.requestAnalyticsWithFallback(app, [
1889
+ {
1890
+ path: `/paywall_items/${paywallItemId}`,
1891
+ method: "DELETE",
1892
+ },
1893
+ {
1894
+ path: `/api/v1/paywall_items/${paywallItemId}`,
1895
+ method: "DELETE",
1896
+ },
1897
+ ], "delete paywall item");
1898
+ return {
1899
+ paywallItemId: options.paywallItemId,
1900
+ sourcePath: result.sourcePath,
1901
+ status: result.status,
1902
+ payload: result.payload,
1903
+ };
1904
+ }
1905
+ async deletePlacementItem(app, options) {
1906
+ const apphudAppId = options.apphudAppId ?? app.appId;
1907
+ const encodedAppId = encodeURIComponent(apphudAppId);
1908
+ const placementId = encodeURIComponent(options.placementId);
1909
+ const placementItemId = encodeURIComponent(options.placementItemId);
1910
+ const result = await this.requestAnalyticsWithFallback(app, [
1911
+ {
1912
+ path: `/apps/${encodedAppId}/placements/${placementId}/items/${placementItemId}`,
1913
+ method: "DELETE",
1914
+ },
1915
+ {
1916
+ path: `/api/v1/apps/${encodedAppId}/placements/${placementId}/items/${placementItemId}`,
1917
+ method: "DELETE",
1918
+ },
1919
+ ], "delete placement item");
1920
+ return {
1921
+ apphudAppId,
1922
+ placementId: options.placementId,
1923
+ placementItemId: options.placementItemId,
1924
+ sourcePath: result.sourcePath,
1925
+ status: result.status,
1926
+ payload: result.payload,
1927
+ };
1928
+ }
1929
+ async deletePlacement(app, options) {
1930
+ const apphudAppId = options.apphudAppId ?? app.appId;
1931
+ const encodedAppId = encodeURIComponent(apphudAppId);
1932
+ const placementId = encodeURIComponent(options.placementId);
1933
+ const result = await this.requestAnalyticsWithFallback(app, [
1934
+ {
1935
+ path: `/apps/${encodedAppId}/placements/${placementId}`,
1936
+ method: "DELETE",
1937
+ },
1938
+ {
1939
+ path: `/api/v1/apps/${encodedAppId}/placements/${placementId}`,
1940
+ method: "DELETE",
1941
+ },
1942
+ {
1943
+ path: `/placements/${placementId}`,
1944
+ method: "DELETE",
1945
+ },
1946
+ ], "delete placement");
1947
+ return {
1948
+ apphudAppId,
1949
+ placementId: options.placementId,
1950
+ sourcePath: result.sourcePath,
1951
+ status: result.status,
1952
+ payload: result.payload,
1953
+ };
1954
+ }
1955
+ async deletePaywallConfig(app, options) {
1956
+ const apphudAppId = options.apphudAppId ?? app.appId;
1957
+ const encodedAppId = encodeURIComponent(apphudAppId);
1958
+ const paywallConfigId = encodeURIComponent(options.paywallConfigId);
1959
+ const result = await this.requestAnalyticsWithFallback(app, [
1960
+ {
1961
+ path: `/paywall_configs/${paywallConfigId}`,
1962
+ method: "DELETE",
1963
+ },
1964
+ {
1965
+ path: `/api/v1/paywall_configs/${paywallConfigId}`,
1966
+ method: "DELETE",
1967
+ },
1968
+ {
1969
+ path: `/apps/${encodedAppId}/paywall_configs/${paywallConfigId}`,
1970
+ method: "DELETE",
1971
+ },
1972
+ ], "delete paywall config");
1973
+ return {
1974
+ apphudAppId,
1975
+ paywallConfigId: options.paywallConfigId,
1976
+ sourcePath: result.sourcePath,
1977
+ status: result.status,
1978
+ payload: result.payload,
1979
+ };
1980
+ }
1981
+ async requestAnalyticsWithFallback(app, attempts, operation) {
1982
+ let lastError;
1983
+ for (const attempt of attempts) {
1984
+ try {
1985
+ const response = await this.requestAnalytics({
1986
+ app,
1987
+ path: attempt.path,
1988
+ method: attempt.method,
1989
+ query: attempt.query,
1990
+ body: attempt.body,
1991
+ });
1992
+ return {
1993
+ status: response.status,
1994
+ payload: response.payload,
1995
+ sourcePath: attempt.path,
1996
+ };
1997
+ }
1998
+ catch (error) {
1999
+ if (!shouldTryNextAnalyticsCandidate(error)) {
2000
+ throw error;
2001
+ }
2002
+ lastError = error;
2003
+ }
2004
+ }
2005
+ if (lastError && isApphudMcpError(lastError)) {
2006
+ const status = getAnalyticsStatusFromError(lastError);
2007
+ if (status === 404 || status === 405) {
2008
+ throw new ApphudMcpError("APPHUD_ANALYTICS_API_UNAVAILABLE", `Cannot ${operation}: endpoint is unavailable for this Apphud profile`, {
2009
+ statusCode: 403,
2010
+ actionHint: "Check Apphud dashboard permissions and endpoint availability for current account profile",
2011
+ details: {
2012
+ status,
2013
+ attempted_paths: attempts.map((attempt) => `${attempt.method} ${attempt.path}`),
2014
+ },
2015
+ });
2016
+ }
2017
+ }
2018
+ if (lastError) {
2019
+ throw lastError;
2020
+ }
2021
+ throw new ApphudMcpError("UPSTREAM_ERROR", `Cannot ${operation}: no endpoint attempts configured`, {
2022
+ statusCode: 502,
2023
+ details: {
2024
+ attempted_paths: attempts.map((attempt) => `${attempt.method} ${attempt.path}`),
2025
+ },
2026
+ });
2027
+ }
1790
2028
  async requestAnalytics(input) {
1791
2029
  const baseUrl = this.config.apphudAnalyticsApiBaseUrl.replace(/\/$/, "");
1792
2030
  const cleanPath = this.normalizeAnalyticsPath(input.path);
@@ -1800,7 +2038,7 @@ export class ApphudClient {
1800
2038
  }
1801
2039
  const headers = new Headers();
1802
2040
  headers.set("Accept", "application/json");
1803
- if (input.method !== "GET") {
2041
+ if (input.method === "POST" || input.method === "PUT") {
1804
2042
  headers.set("Content-Type", "application/json");
1805
2043
  }
1806
2044
  const analyticsSecretRef = this.config.apphudAnalyticsAuthSecretRef ??
@@ -1909,10 +2147,11 @@ export class ApphudClient {
1909
2147
  async executeAnalyticsHttpRequest(url, method, headers, body, endpoint) {
1910
2148
  let response;
1911
2149
  try {
2150
+ const hasBody = method === "POST" || method === "PUT" || (method === "DELETE" && body !== undefined && Object.keys(body).length > 0);
1912
2151
  response = await fetch(url, {
1913
2152
  method,
1914
2153
  headers,
1915
- body: method === "GET" ? undefined : JSON.stringify(body ?? {}),
2154
+ body: hasBody ? JSON.stringify(body ?? {}) : undefined,
1916
2155
  signal: AbortSignal.timeout(10_000),
1917
2156
  });
1918
2157
  }
@@ -27,6 +27,12 @@ const baseInputShape = {
27
27
  };
28
28
  export const REMOTE_TOOL_NAMES = [
29
29
  "apphud_apps_list",
30
+ "apphud_paywall_item_add",
31
+ "apphud_placement_item_set_enabled",
32
+ "apphud_paywall_item_delete",
33
+ "apphud_placement_item_delete",
34
+ "apphud_placement_delete",
35
+ "apphud_paywall_config_delete",
30
36
  "apphud_analytics_events_list",
31
37
  "apphud_analytics_active_subscriptions",
32
38
  "apphud_analytics_capabilities_get",
@@ -58,6 +64,118 @@ const REMOTE_TOOL_DEFINITIONS = {
58
64
  include_raw: input.include_raw,
59
65
  }),
60
66
  },
67
+ apphud_paywall_item_add: {
68
+ description: "Add product to paywall config (creates paywall item)",
69
+ inputShape: {
70
+ ...baseInputShape,
71
+ app_id: z.string().min(1).optional(),
72
+ apphud_app_id: z.string().min(1).optional(),
73
+ paywall_config_id: z.string().min(1),
74
+ product_bundle_id: z.string().min(1),
75
+ include_raw: z.boolean().optional(),
76
+ },
77
+ appIdSelector: appIdFromInput,
78
+ execute: async (container, auth, input) => container.analyticsService.paywallItemAdd(auth, {
79
+ app_id: input.app_id,
80
+ apphud_app_id: input.apphud_app_id,
81
+ paywall_config_id: input.paywall_config_id,
82
+ product_bundle_id: input.product_bundle_id,
83
+ include_raw: input.include_raw,
84
+ }),
85
+ },
86
+ apphud_placement_item_set_enabled: {
87
+ description: "Enable/disable placement item state",
88
+ inputShape: {
89
+ ...baseInputShape,
90
+ app_id: z.string().min(1).optional(),
91
+ apphud_app_id: z.string().min(1).optional(),
92
+ placement_id: z.string().min(1),
93
+ placement_item_id: z.string().min(1),
94
+ paywall_config_id: z.string().min(1).optional(),
95
+ enabled: z.boolean(),
96
+ include_raw: z.boolean().optional(),
97
+ },
98
+ appIdSelector: appIdFromInput,
99
+ execute: async (container, auth, input) => container.analyticsService.placementItemSetEnabled(auth, {
100
+ app_id: input.app_id,
101
+ apphud_app_id: input.apphud_app_id,
102
+ placement_id: input.placement_id,
103
+ placement_item_id: input.placement_item_id,
104
+ paywall_config_id: input.paywall_config_id,
105
+ enabled: input.enabled,
106
+ include_raw: input.include_raw,
107
+ }),
108
+ },
109
+ apphud_paywall_item_delete: {
110
+ description: "Delete paywall item by id",
111
+ inputShape: {
112
+ ...baseInputShape,
113
+ app_id: z.string().min(1).optional(),
114
+ apphud_app_id: z.string().min(1).optional(),
115
+ paywall_item_id: z.string().min(1),
116
+ include_raw: z.boolean().optional(),
117
+ },
118
+ appIdSelector: appIdFromInput,
119
+ execute: async (container, auth, input) => container.analyticsService.paywallItemDelete(auth, {
120
+ app_id: input.app_id,
121
+ apphud_app_id: input.apphud_app_id,
122
+ paywall_item_id: input.paywall_item_id,
123
+ include_raw: input.include_raw,
124
+ }),
125
+ },
126
+ apphud_placement_item_delete: {
127
+ description: "Delete placement item by id",
128
+ inputShape: {
129
+ ...baseInputShape,
130
+ app_id: z.string().min(1).optional(),
131
+ apphud_app_id: z.string().min(1).optional(),
132
+ placement_id: z.string().min(1),
133
+ placement_item_id: z.string().min(1),
134
+ include_raw: z.boolean().optional(),
135
+ },
136
+ appIdSelector: appIdFromInput,
137
+ execute: async (container, auth, input) => container.analyticsService.placementItemDelete(auth, {
138
+ app_id: input.app_id,
139
+ apphud_app_id: input.apphud_app_id,
140
+ placement_id: input.placement_id,
141
+ placement_item_id: input.placement_item_id,
142
+ include_raw: input.include_raw,
143
+ }),
144
+ },
145
+ apphud_placement_delete: {
146
+ description: "Delete placement by id",
147
+ inputShape: {
148
+ ...baseInputShape,
149
+ app_id: z.string().min(1).optional(),
150
+ apphud_app_id: z.string().min(1).optional(),
151
+ placement_id: z.string().min(1),
152
+ include_raw: z.boolean().optional(),
153
+ },
154
+ appIdSelector: appIdFromInput,
155
+ execute: async (container, auth, input) => container.analyticsService.placementDelete(auth, {
156
+ app_id: input.app_id,
157
+ apphud_app_id: input.apphud_app_id,
158
+ placement_id: input.placement_id,
159
+ include_raw: input.include_raw,
160
+ }),
161
+ },
162
+ apphud_paywall_config_delete: {
163
+ description: "Delete paywall config by id",
164
+ inputShape: {
165
+ ...baseInputShape,
166
+ app_id: z.string().min(1).optional(),
167
+ apphud_app_id: z.string().min(1).optional(),
168
+ paywall_config_id: z.string().min(1),
169
+ include_raw: z.boolean().optional(),
170
+ },
171
+ appIdSelector: appIdFromInput,
172
+ execute: async (container, auth, input) => container.analyticsService.paywallConfigDelete(auth, {
173
+ app_id: input.app_id,
174
+ apphud_app_id: input.apphud_app_id,
175
+ paywall_config_id: input.paywall_config_id,
176
+ include_raw: input.include_raw,
177
+ }),
178
+ },
61
179
  apphud_analytics_events_list: {
62
180
  description: "List raw subscription events from Apphud dashboard event feed",
63
181
  inputShape: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apphud-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "description": "MCP server for Apphud dashboard analytics",
6
6
  "license": "MIT",