@fload-ai/mcp 0.1.1 → 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 +79 -46
- package/dist/.tsbuildinfo +1 -0
- package/dist/api-client.d.ts +11 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +53 -0
- package/dist/api-client.js.map +7 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +2012 -0
- package/dist/bin.js.map +7 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +933 -247
- package/dist/index.js.map +4 -4
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/rate-limiter.d.ts +12 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/tools/actions.d.ts +69 -0
- package/dist/tools/actions.d.ts.map +1 -0
- package/dist/tools/ads.d.ts +35 -0
- package/dist/tools/ads.d.ts.map +1 -0
- package/dist/tools/agents.d.ts +149 -0
- package/dist/tools/agents.d.ts.map +1 -0
- package/dist/tools/analytics.d.ts +84 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/anomalies.d.ts +107 -0
- package/dist/tools/anomalies.d.ts.map +1 -0
- package/dist/tools/apps.d.ts +49 -0
- package/dist/tools/apps.d.ts.map +1 -0
- package/dist/tools/aso.d.ts +135 -0
- package/dist/tools/aso.d.ts.map +1 -0
- package/dist/tools/chat.d.ts +69 -0
- package/dist/tools/chat.d.ts.map +1 -0
- package/dist/tools/dashboard.d.ts +17 -0
- package/dist/tools/dashboard.d.ts.map +1 -0
- package/dist/tools/forecasting.d.ts +26 -0
- package/dist/tools/forecasting.d.ts.map +1 -0
- package/dist/tools/growth.d.ts +43 -0
- package/dist/tools/growth.d.ts.map +1 -0
- package/dist/tools/index.d.ts +20 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1952 -0
- package/dist/tools/index.js.map +7 -0
- package/dist/tools/reviews.d.ts +119 -0
- package/dist/tools/reviews.d.ts.map +1 -0
- package/package.json +18 -3
package/dist/index.js
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// src/config.ts
|
|
7
|
-
import "dotenv/config";
|
|
8
|
-
import { readFileSync, existsSync } from "fs";
|
|
9
|
-
import { homedir } from "os";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
function loadConfig() {
|
|
12
|
-
const apiKey = process.env.FLOAD_API_KEY;
|
|
13
|
-
const apiUrl = process.env.FLOAD_API_URL || "https://api.fload.com";
|
|
14
|
-
if (apiKey) {
|
|
15
|
-
return { apiKey, apiUrl };
|
|
1
|
+
// src/api-client.ts
|
|
2
|
+
var FloadApiClient = class {
|
|
3
|
+
constructor(baseUrl, apiKey) {
|
|
4
|
+
this.baseUrl = baseUrl;
|
|
5
|
+
this.apiKey = apiKey;
|
|
16
6
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
7
|
+
async request(path, options = {}) {
|
|
8
|
+
const url = `${this.baseUrl}${path}`;
|
|
9
|
+
const response = await fetch(url, {
|
|
10
|
+
...options,
|
|
11
|
+
headers: {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
26
15
|
}
|
|
27
|
-
}
|
|
28
|
-
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
19
|
+
throw new Error(error.message || `API error: ${response.status}`);
|
|
29
20
|
}
|
|
21
|
+
return response.json();
|
|
30
22
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
async get(path, params) {
|
|
24
|
+
const searchParams = new URLSearchParams();
|
|
25
|
+
if (params) {
|
|
26
|
+
for (const [key, value] of Object.entries(params)) {
|
|
27
|
+
if (value !== void 0)
|
|
28
|
+
searchParams.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const query = searchParams.toString();
|
|
32
|
+
return this.request(`${path}${query ? `?${query}` : ""}`);
|
|
33
|
+
}
|
|
34
|
+
async post(path, body) {
|
|
35
|
+
return this.request(path, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
body: body ? JSON.stringify(body) : void 0
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async patch(path, body) {
|
|
41
|
+
return this.request(path, {
|
|
42
|
+
method: "PATCH",
|
|
43
|
+
body: body ? JSON.stringify(body) : void 0
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async delete(path) {
|
|
47
|
+
return this.request(path, { method: "DELETE" });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
35
50
|
|
|
36
51
|
// src/server.ts
|
|
37
52
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -199,6 +214,19 @@ var getReviewsSchema = z2.object({
|
|
|
199
214
|
limit: z2.number().int().min(1).max(200).default(50).describe("Maximum number of reviews to return"),
|
|
200
215
|
sortBy: z2.enum(["date", "rating"]).default("date").describe("Sort reviews by date or rating")
|
|
201
216
|
});
|
|
217
|
+
var generateReviewReplySchema = z2.object({
|
|
218
|
+
reviewId: z2.string().describe("The review UUID to generate a reply for"),
|
|
219
|
+
assetId: z2.string().uuid().describe("The app UUID the review belongs to")
|
|
220
|
+
});
|
|
221
|
+
var sendReviewReplySchema = z2.object({
|
|
222
|
+
reviewId: z2.string().describe("The review UUID to reply to"),
|
|
223
|
+
assetId: z2.string().uuid().describe("The app UUID the review belongs to"),
|
|
224
|
+
response: z2.string().describe("The reply text to send")
|
|
225
|
+
});
|
|
226
|
+
var translateReviewSchema = z2.object({
|
|
227
|
+
reviewId: z2.string().describe("The review UUID to translate"),
|
|
228
|
+
assetId: z2.string().uuid().describe("The app UUID the review belongs to")
|
|
229
|
+
});
|
|
202
230
|
async function getReviews(input, client) {
|
|
203
231
|
try {
|
|
204
232
|
let assetId = input.assetId;
|
|
@@ -278,6 +306,82 @@ async function getReviews(input, client) {
|
|
|
278
306
|
};
|
|
279
307
|
}
|
|
280
308
|
}
|
|
309
|
+
async function generateReviewReply(input, client) {
|
|
310
|
+
try {
|
|
311
|
+
const response = await client.post(`/api/reviews/${input.reviewId}/generate-reply`, {
|
|
312
|
+
assetId: input.assetId
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
content: [
|
|
316
|
+
{
|
|
317
|
+
type: "text",
|
|
318
|
+
text: formatAsJson(response)
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
return {
|
|
324
|
+
content: [
|
|
325
|
+
{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: formatError(error)
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
isError: true
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function sendReviewReply(input, client) {
|
|
335
|
+
try {
|
|
336
|
+
const response = await client.post(`/api/reviews/${input.reviewId}/respond`, {
|
|
337
|
+
assetId: input.assetId,
|
|
338
|
+
response: input.response
|
|
339
|
+
});
|
|
340
|
+
return {
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: "text",
|
|
344
|
+
text: formatAsJson(response)
|
|
345
|
+
}
|
|
346
|
+
]
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return {
|
|
350
|
+
content: [
|
|
351
|
+
{
|
|
352
|
+
type: "text",
|
|
353
|
+
text: formatError(error)
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
isError: true
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function translateReview(input, client) {
|
|
361
|
+
try {
|
|
362
|
+
const response = await client.post(`/api/reviews/${input.reviewId}/translate`, {
|
|
363
|
+
assetId: input.assetId
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
content: [
|
|
367
|
+
{
|
|
368
|
+
type: "text",
|
|
369
|
+
text: formatAsJson(response)
|
|
370
|
+
}
|
|
371
|
+
]
|
|
372
|
+
};
|
|
373
|
+
} catch (error) {
|
|
374
|
+
return {
|
|
375
|
+
content: [
|
|
376
|
+
{
|
|
377
|
+
type: "text",
|
|
378
|
+
text: formatError(error)
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
isError: true
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
281
385
|
|
|
282
386
|
// src/tools/analytics.ts
|
|
283
387
|
import { z as z3 } from "zod";
|
|
@@ -507,6 +611,19 @@ var getAgentRunHistorySchema = z4.object({
|
|
|
507
611
|
assetId: z4.string().uuid().optional().describe("App UUID to filter runs"),
|
|
508
612
|
limit: z4.number().int().min(1).max(100).default(20).describe("Maximum number of runs to return")
|
|
509
613
|
});
|
|
614
|
+
var triggerAgentRunSchema = z4.object({
|
|
615
|
+
agentId: z4.string().describe("The agent ID to trigger a run for"),
|
|
616
|
+
assetId: z4.string().uuid().optional().describe("Optional app UUID to run the agent against")
|
|
617
|
+
});
|
|
618
|
+
var pauseAgentSchema = z4.object({
|
|
619
|
+
agentId: z4.string().describe("The agent ID to pause")
|
|
620
|
+
});
|
|
621
|
+
var resumeAgentSchema = z4.object({
|
|
622
|
+
agentId: z4.string().describe("The agent ID to resume")
|
|
623
|
+
});
|
|
624
|
+
var getAgentActivitySchema = z4.object({
|
|
625
|
+
agentId: z4.string().describe("The agent ID to get activity for")
|
|
626
|
+
});
|
|
510
627
|
async function listAgents(_input, client) {
|
|
511
628
|
try {
|
|
512
629
|
let apiAgents = null;
|
|
@@ -643,6 +760,101 @@ async function getAgentRunHistory(input, client) {
|
|
|
643
760
|
};
|
|
644
761
|
}
|
|
645
762
|
}
|
|
763
|
+
async function triggerAgentRun(input, client) {
|
|
764
|
+
try {
|
|
765
|
+
const body = {};
|
|
766
|
+
if (input.assetId)
|
|
767
|
+
body.assetId = input.assetId;
|
|
768
|
+
const response = await client.post(`/api/agents/${input.agentId}/run`, body);
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: "text",
|
|
773
|
+
text: formatAsJson(response.data || response)
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
};
|
|
777
|
+
} catch (error) {
|
|
778
|
+
return {
|
|
779
|
+
content: [
|
|
780
|
+
{
|
|
781
|
+
type: "text",
|
|
782
|
+
text: formatError(error)
|
|
783
|
+
}
|
|
784
|
+
],
|
|
785
|
+
isError: true
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async function pauseAgent(input, client) {
|
|
790
|
+
try {
|
|
791
|
+
const response = await client.post(`/api/agents/${input.agentId}/pause`);
|
|
792
|
+
return {
|
|
793
|
+
content: [
|
|
794
|
+
{
|
|
795
|
+
type: "text",
|
|
796
|
+
text: formatAsJson(response.data || response)
|
|
797
|
+
}
|
|
798
|
+
]
|
|
799
|
+
};
|
|
800
|
+
} catch (error) {
|
|
801
|
+
return {
|
|
802
|
+
content: [
|
|
803
|
+
{
|
|
804
|
+
type: "text",
|
|
805
|
+
text: formatError(error)
|
|
806
|
+
}
|
|
807
|
+
],
|
|
808
|
+
isError: true
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function resumeAgent(input, client) {
|
|
813
|
+
try {
|
|
814
|
+
const response = await client.post(`/api/agents/${input.agentId}/resume`);
|
|
815
|
+
return {
|
|
816
|
+
content: [
|
|
817
|
+
{
|
|
818
|
+
type: "text",
|
|
819
|
+
text: formatAsJson(response.data || response)
|
|
820
|
+
}
|
|
821
|
+
]
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
content: [
|
|
826
|
+
{
|
|
827
|
+
type: "text",
|
|
828
|
+
text: formatError(error)
|
|
829
|
+
}
|
|
830
|
+
],
|
|
831
|
+
isError: true
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async function getAgentActivity(input, client) {
|
|
836
|
+
try {
|
|
837
|
+
const response = await client.get(`/api/agents/${input.agentId}/activity`);
|
|
838
|
+
return {
|
|
839
|
+
content: [
|
|
840
|
+
{
|
|
841
|
+
type: "text",
|
|
842
|
+
text: formatAsJson(response.data || response)
|
|
843
|
+
}
|
|
844
|
+
]
|
|
845
|
+
};
|
|
846
|
+
} catch (error) {
|
|
847
|
+
return {
|
|
848
|
+
content: [
|
|
849
|
+
{
|
|
850
|
+
type: "text",
|
|
851
|
+
text: formatError(error)
|
|
852
|
+
}
|
|
853
|
+
],
|
|
854
|
+
isError: true
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
646
858
|
|
|
647
859
|
// src/tools/anomalies.ts
|
|
648
860
|
import { z as z5 } from "zod";
|
|
@@ -657,6 +869,15 @@ var getAnomaliesSchema = z5.object({
|
|
|
657
869
|
excludeDismissed: z5.boolean().default(true).describe("Exclude dismissed anomalies (default: true)"),
|
|
658
870
|
limit: z5.number().int().min(1).max(100).default(50).describe("Maximum number of anomalies to return")
|
|
659
871
|
});
|
|
872
|
+
var getAnomalyDetailSchema = z5.object({
|
|
873
|
+
id: z5.string().uuid().describe("The anomaly UUID to get details for")
|
|
874
|
+
});
|
|
875
|
+
var acknowledgeAnomalySchema = z5.object({
|
|
876
|
+
id: z5.string().uuid().describe("The anomaly UUID to acknowledge")
|
|
877
|
+
});
|
|
878
|
+
var dismissAnomalySchema = z5.object({
|
|
879
|
+
id: z5.string().uuid().describe("The anomaly UUID to dismiss")
|
|
880
|
+
});
|
|
660
881
|
async function getAnomalies(input, client) {
|
|
661
882
|
try {
|
|
662
883
|
const params = {
|
|
@@ -721,22 +942,91 @@ async function getAnomalies(input, client) {
|
|
|
721
942
|
};
|
|
722
943
|
}
|
|
723
944
|
}
|
|
724
|
-
|
|
725
|
-
// src/tools/ads.ts
|
|
726
|
-
import { z as z6 } from "zod";
|
|
727
|
-
var getAdsPerformanceSchema = z6.object({
|
|
728
|
-
assetId: z6.string().uuid().optional().describe("Filter by app UUID"),
|
|
729
|
-
platform: z6.enum(["apple_search_ads", "google_ads", "meta_ads", "tiktok_ads"]).optional().describe("Filter by ad platform"),
|
|
730
|
-
fromDate: z6.string().optional().describe("Start date for performance data (YYYY-MM-DD)"),
|
|
731
|
-
toDate: z6.string().optional().describe("End date for performance data (YYYY-MM-DD)"),
|
|
732
|
-
limit: z6.number().int().min(1).max(100).default(50).describe("Maximum number of campaigns to return")
|
|
733
|
-
});
|
|
734
|
-
async function getAdsPerformance(input, client) {
|
|
945
|
+
async function getAnomalyDetail(input, client) {
|
|
735
946
|
try {
|
|
736
|
-
const
|
|
737
|
-
|
|
947
|
+
const response = await client.get(`/api/anomalies/${input.id}`);
|
|
948
|
+
return {
|
|
949
|
+
content: [
|
|
950
|
+
{
|
|
951
|
+
type: "text",
|
|
952
|
+
text: formatAsJson(response)
|
|
953
|
+
}
|
|
954
|
+
]
|
|
738
955
|
};
|
|
739
|
-
|
|
956
|
+
} catch (error) {
|
|
957
|
+
return {
|
|
958
|
+
content: [
|
|
959
|
+
{
|
|
960
|
+
type: "text",
|
|
961
|
+
text: formatError(error)
|
|
962
|
+
}
|
|
963
|
+
],
|
|
964
|
+
isError: true
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
async function acknowledgeAnomaly(input, client) {
|
|
969
|
+
try {
|
|
970
|
+
const response = await client.patch(`/api/anomalies/${input.id}/acknowledge`);
|
|
971
|
+
return {
|
|
972
|
+
content: [
|
|
973
|
+
{
|
|
974
|
+
type: "text",
|
|
975
|
+
text: formatAsJson(response)
|
|
976
|
+
}
|
|
977
|
+
]
|
|
978
|
+
};
|
|
979
|
+
} catch (error) {
|
|
980
|
+
return {
|
|
981
|
+
content: [
|
|
982
|
+
{
|
|
983
|
+
type: "text",
|
|
984
|
+
text: formatError(error)
|
|
985
|
+
}
|
|
986
|
+
],
|
|
987
|
+
isError: true
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function dismissAnomaly(input, client) {
|
|
992
|
+
try {
|
|
993
|
+
const response = await client.patch(`/api/anomalies/${input.id}/dismiss`);
|
|
994
|
+
return {
|
|
995
|
+
content: [
|
|
996
|
+
{
|
|
997
|
+
type: "text",
|
|
998
|
+
text: formatAsJson(response)
|
|
999
|
+
}
|
|
1000
|
+
]
|
|
1001
|
+
};
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
return {
|
|
1004
|
+
content: [
|
|
1005
|
+
{
|
|
1006
|
+
type: "text",
|
|
1007
|
+
text: formatError(error)
|
|
1008
|
+
}
|
|
1009
|
+
],
|
|
1010
|
+
isError: true
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/tools/ads.ts
|
|
1016
|
+
import { z as z6 } from "zod";
|
|
1017
|
+
var getAdsPerformanceSchema = z6.object({
|
|
1018
|
+
assetId: z6.string().uuid().optional().describe("Filter by app UUID"),
|
|
1019
|
+
platform: z6.enum(["apple_search_ads", "google_ads", "meta_ads", "tiktok_ads"]).optional().describe("Filter by ad platform"),
|
|
1020
|
+
fromDate: z6.string().optional().describe("Start date for performance data (YYYY-MM-DD)"),
|
|
1021
|
+
toDate: z6.string().optional().describe("End date for performance data (YYYY-MM-DD)"),
|
|
1022
|
+
limit: z6.number().int().min(1).max(100).default(50).describe("Maximum number of campaigns to return")
|
|
1023
|
+
});
|
|
1024
|
+
async function getAdsPerformance(input, client) {
|
|
1025
|
+
try {
|
|
1026
|
+
const params = {
|
|
1027
|
+
limit: input.limit
|
|
1028
|
+
};
|
|
1029
|
+
if (input.assetId)
|
|
740
1030
|
params.assetId = input.assetId;
|
|
741
1031
|
if (input.platform)
|
|
742
1032
|
params.platform = input.platform;
|
|
@@ -1030,262 +1320,658 @@ async function rejectAction(input, client) {
|
|
|
1030
1320
|
}
|
|
1031
1321
|
}
|
|
1032
1322
|
|
|
1033
|
-
// src/
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1323
|
+
// src/tools/aso.ts
|
|
1324
|
+
import { z as z11 } from "zod";
|
|
1325
|
+
var getAsoSummarySchema = z11.object({
|
|
1326
|
+
assetId: z11.string().uuid().describe("App UUID to get ASO summary for")
|
|
1327
|
+
});
|
|
1328
|
+
var getAsoRecommendationsSchema = z11.object({
|
|
1329
|
+
assetId: z11.string().uuid().describe("App UUID to get ASO recommendations for")
|
|
1330
|
+
});
|
|
1331
|
+
var getAsoKeywordsSchema = z11.object({
|
|
1332
|
+
assetId: z11.string().uuid().describe("App UUID to get keyword intelligence for"),
|
|
1333
|
+
locale: z11.string().optional().describe('Locale code to filter keywords (e.g., "en-US", "de-DE"). Returns all locales if omitted.')
|
|
1334
|
+
});
|
|
1335
|
+
var getAsoExperimentsSchema = z11.object({
|
|
1336
|
+
assetId: z11.string().uuid().describe("App UUID to list ASO experiments for"),
|
|
1337
|
+
status: z11.enum(["proposed", "approved", "applied", "measuring", "completed", "reverted"]).optional().describe("Filter experiments by status"),
|
|
1338
|
+
limit: z11.number().int().min(1).max(100).default(20).describe("Maximum number of experiments to return"),
|
|
1339
|
+
offset: z11.number().int().min(0).default(0).describe("Number of experiments to skip for pagination")
|
|
1340
|
+
});
|
|
1341
|
+
var getAsoLocaleSnapshotsSchema = z11.object({
|
|
1342
|
+
assetId: z11.string().uuid().describe("App UUID to get locale snapshots for")
|
|
1343
|
+
});
|
|
1344
|
+
var triggerAsoAnalysisSchema = z11.object({
|
|
1345
|
+
assetId: z11.string().uuid().describe("App UUID to trigger ASO analysis for")
|
|
1346
|
+
});
|
|
1347
|
+
async function getAsoSummary(input, client) {
|
|
1348
|
+
try {
|
|
1349
|
+
const response = await client.get(`/api/assets/${input.assetId}/aso/summary`);
|
|
1350
|
+
return {
|
|
1351
|
+
content: [
|
|
1352
|
+
{
|
|
1353
|
+
type: "text",
|
|
1354
|
+
text: formatAsJson(response)
|
|
1355
|
+
}
|
|
1356
|
+
]
|
|
1357
|
+
};
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
return {
|
|
1360
|
+
content: [
|
|
1361
|
+
{
|
|
1362
|
+
type: "text",
|
|
1363
|
+
text: formatError(error)
|
|
1364
|
+
}
|
|
1365
|
+
],
|
|
1366
|
+
isError: true
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
async function getAsoRecommendations(input, client) {
|
|
1371
|
+
try {
|
|
1372
|
+
const response = await client.get(`/api/assets/${input.assetId}/aso/recommendations`);
|
|
1373
|
+
return {
|
|
1374
|
+
content: [
|
|
1375
|
+
{
|
|
1376
|
+
type: "text",
|
|
1377
|
+
text: formatAsJson(response)
|
|
1378
|
+
}
|
|
1379
|
+
]
|
|
1380
|
+
};
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
return {
|
|
1383
|
+
content: [
|
|
1384
|
+
{
|
|
1385
|
+
type: "text",
|
|
1386
|
+
text: formatError(error)
|
|
1387
|
+
}
|
|
1388
|
+
],
|
|
1389
|
+
isError: true
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async function getAsoKeywords(input, client) {
|
|
1394
|
+
try {
|
|
1395
|
+
const params = {};
|
|
1396
|
+
if (input.locale)
|
|
1397
|
+
params.locale = input.locale;
|
|
1398
|
+
const response = await client.get(`/api/assets/${input.assetId}/aso/keyword-intelligence`, params);
|
|
1399
|
+
return {
|
|
1400
|
+
content: [
|
|
1401
|
+
{
|
|
1402
|
+
type: "text",
|
|
1403
|
+
text: formatAsJson(response)
|
|
1404
|
+
}
|
|
1405
|
+
]
|
|
1406
|
+
};
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
return {
|
|
1409
|
+
content: [
|
|
1410
|
+
{
|
|
1411
|
+
type: "text",
|
|
1412
|
+
text: formatError(error)
|
|
1413
|
+
}
|
|
1414
|
+
],
|
|
1415
|
+
isError: true
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
async function getAsoExperiments(input, client) {
|
|
1420
|
+
try {
|
|
1421
|
+
const params = {
|
|
1422
|
+
limit: input.limit,
|
|
1423
|
+
offset: input.offset
|
|
1424
|
+
};
|
|
1425
|
+
if (input.status)
|
|
1426
|
+
params.status = input.status;
|
|
1427
|
+
const response = await client.get(`/api/assets/${input.assetId}/aso/experiments`, params);
|
|
1428
|
+
return {
|
|
1429
|
+
content: [
|
|
1430
|
+
{
|
|
1431
|
+
type: "text",
|
|
1432
|
+
text: formatAsJson(response)
|
|
1433
|
+
}
|
|
1434
|
+
]
|
|
1435
|
+
};
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
return {
|
|
1438
|
+
content: [
|
|
1439
|
+
{
|
|
1440
|
+
type: "text",
|
|
1441
|
+
text: formatError(error)
|
|
1442
|
+
}
|
|
1443
|
+
],
|
|
1444
|
+
isError: true
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
async function getAsoLocaleSnapshots(input, client) {
|
|
1449
|
+
try {
|
|
1450
|
+
const response = await client.get(`/api/assets/${input.assetId}/aso/locale-snapshots`);
|
|
1451
|
+
return {
|
|
1452
|
+
content: [
|
|
1453
|
+
{
|
|
1454
|
+
type: "text",
|
|
1455
|
+
text: formatAsJson(response)
|
|
1456
|
+
}
|
|
1457
|
+
]
|
|
1458
|
+
};
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
return {
|
|
1461
|
+
content: [
|
|
1462
|
+
{
|
|
1463
|
+
type: "text",
|
|
1464
|
+
text: formatError(error)
|
|
1465
|
+
}
|
|
1466
|
+
],
|
|
1467
|
+
isError: true
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
async function triggerAsoAnalysis(input, client) {
|
|
1472
|
+
try {
|
|
1473
|
+
const response = await client.post(`/api/assets/${input.assetId}/aso/analyze`);
|
|
1474
|
+
return {
|
|
1475
|
+
content: [
|
|
1476
|
+
{
|
|
1477
|
+
type: "text",
|
|
1478
|
+
text: formatAsJson(response)
|
|
1479
|
+
}
|
|
1480
|
+
]
|
|
1481
|
+
};
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
return {
|
|
1484
|
+
content: [
|
|
1485
|
+
{
|
|
1486
|
+
type: "text",
|
|
1487
|
+
text: formatError(error)
|
|
1488
|
+
}
|
|
1489
|
+
],
|
|
1490
|
+
isError: true
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/tools/chat.ts
|
|
1496
|
+
import { z as z12 } from "zod";
|
|
1497
|
+
var listConversationsSchema = z12.object({
|
|
1498
|
+
limit: z12.number().int().min(1).max(100).default(20).describe("Maximum number of conversations to return")
|
|
1499
|
+
});
|
|
1500
|
+
var getConversationMessagesSchema = z12.object({
|
|
1501
|
+
conversationId: z12.string().uuid().describe("The conversation UUID to get messages for")
|
|
1502
|
+
});
|
|
1503
|
+
var sendChatMessageSchema = z12.object({
|
|
1504
|
+
message: z12.string().describe("The message to send to the AI chat"),
|
|
1505
|
+
conversationId: z12.string().uuid().optional().describe("Existing conversation UUID to continue. Starts a new conversation if omitted."),
|
|
1506
|
+
agentType: z12.string().optional().describe("Agent type to use for the conversation (e.g., review, monitoring, forecasting)")
|
|
1507
|
+
});
|
|
1508
|
+
async function listConversations(input, client) {
|
|
1509
|
+
try {
|
|
1510
|
+
const response = await client.get("/api/chat/conversations", {
|
|
1511
|
+
limit: input.limit
|
|
1512
|
+
});
|
|
1513
|
+
const conversations = response.data || response.conversations || response;
|
|
1514
|
+
return {
|
|
1515
|
+
content: [
|
|
1516
|
+
{
|
|
1517
|
+
type: "text",
|
|
1518
|
+
text: formatAsJson({
|
|
1519
|
+
totalConversations: Array.isArray(conversations) ? conversations.length : 0,
|
|
1520
|
+
conversations
|
|
1521
|
+
})
|
|
1522
|
+
}
|
|
1523
|
+
]
|
|
1524
|
+
};
|
|
1525
|
+
} catch (error) {
|
|
1526
|
+
return {
|
|
1527
|
+
content: [
|
|
1528
|
+
{
|
|
1529
|
+
type: "text",
|
|
1530
|
+
text: formatError(error)
|
|
1531
|
+
}
|
|
1532
|
+
],
|
|
1533
|
+
isError: true
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
async function getConversationMessages(input, client) {
|
|
1538
|
+
try {
|
|
1539
|
+
const response = await client.get(`/api/chat/messages/${input.conversationId}`);
|
|
1540
|
+
const messages = response.data || response.messages || response;
|
|
1541
|
+
return {
|
|
1542
|
+
content: [
|
|
1543
|
+
{
|
|
1544
|
+
type: "text",
|
|
1545
|
+
text: formatAsJson({
|
|
1546
|
+
conversationId: input.conversationId,
|
|
1547
|
+
totalMessages: Array.isArray(messages) ? messages.length : 0,
|
|
1548
|
+
messages
|
|
1549
|
+
})
|
|
1550
|
+
}
|
|
1551
|
+
]
|
|
1552
|
+
};
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
return {
|
|
1555
|
+
content: [
|
|
1556
|
+
{
|
|
1557
|
+
type: "text",
|
|
1558
|
+
text: formatError(error)
|
|
1559
|
+
}
|
|
1560
|
+
],
|
|
1561
|
+
isError: true
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
async function sendChatMessage(input, client) {
|
|
1566
|
+
try {
|
|
1567
|
+
const body = {
|
|
1568
|
+
message: input.message
|
|
1569
|
+
};
|
|
1570
|
+
if (input.conversationId)
|
|
1571
|
+
body.conversationId = input.conversationId;
|
|
1572
|
+
if (input.agentType)
|
|
1573
|
+
body.agentType = input.agentType;
|
|
1574
|
+
const response = await client.post("/api/chat", body);
|
|
1575
|
+
return {
|
|
1576
|
+
content: [
|
|
1577
|
+
{
|
|
1578
|
+
type: "text",
|
|
1579
|
+
text: formatAsJson(response.data || response)
|
|
1580
|
+
}
|
|
1581
|
+
]
|
|
1582
|
+
};
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
return {
|
|
1585
|
+
content: [
|
|
1586
|
+
{
|
|
1587
|
+
type: "text",
|
|
1588
|
+
text: formatError(error)
|
|
1589
|
+
}
|
|
1590
|
+
],
|
|
1591
|
+
isError: true
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// src/tools/index.ts
|
|
1597
|
+
function registerTools(server, client, options = {}) {
|
|
1598
|
+
const rateLimiter = options.rateLimiter ?? new RateLimiter(100, 6e4);
|
|
1599
|
+
function wrapTool(_toolName, handler) {
|
|
1042
1600
|
return async (input) => {
|
|
1043
1601
|
const rateCheck = rateLimiter.check("default");
|
|
1044
1602
|
if (!rateCheck.allowed) {
|
|
1045
1603
|
return {
|
|
1046
|
-
content: [
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1604
|
+
content: [
|
|
1605
|
+
{
|
|
1606
|
+
type: "text",
|
|
1607
|
+
text: `Rate limit exceeded. Try again in ${Math.ceil(
|
|
1608
|
+
rateCheck.retryAfterMs / 1e3
|
|
1609
|
+
)}s. Limit: 100 requests/minute.`
|
|
1610
|
+
}
|
|
1611
|
+
],
|
|
1050
1612
|
isError: true
|
|
1051
1613
|
};
|
|
1052
1614
|
}
|
|
1053
1615
|
return handler(input, client);
|
|
1054
1616
|
};
|
|
1055
1617
|
}
|
|
1056
|
-
|
|
1618
|
+
function tool(name, title, description, schema, handler, annotations) {
|
|
1619
|
+
server.registerTool(
|
|
1620
|
+
name,
|
|
1621
|
+
{
|
|
1622
|
+
title,
|
|
1623
|
+
description,
|
|
1624
|
+
inputSchema: schema.shape,
|
|
1625
|
+
annotations: { title, ...annotations }
|
|
1626
|
+
},
|
|
1627
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1628
|
+
wrapTool(name, handler)
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
tool(
|
|
1057
1632
|
"list_apps",
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1633
|
+
"List apps",
|
|
1634
|
+
"List all mobile apps in your Fload organization. Returns app metadata including name, bundle ID, platform (iOS/Android), icon URL, and category.",
|
|
1635
|
+
listAppsSchema,
|
|
1636
|
+
listApps,
|
|
1637
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1063
1638
|
);
|
|
1064
|
-
|
|
1639
|
+
tool(
|
|
1065
1640
|
"get_app_details",
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1641
|
+
"Get app details",
|
|
1642
|
+
"Get detailed information about a specific app, including metadata, connected data sources (App Store Connect, Google Play Console), and sync status. Provide either assetId (UUID) or bundleId.",
|
|
1643
|
+
getAppDetailsSchema,
|
|
1644
|
+
getAppDetails,
|
|
1645
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1071
1646
|
);
|
|
1072
|
-
|
|
1647
|
+
tool(
|
|
1073
1648
|
"get_reviews",
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1649
|
+
"Get reviews",
|
|
1650
|
+
"Get app reviews with flexible filtering. Filter by app (assetId or bundleId), platform, rating (1-5 stars), replied status, and date range. Returns reviews with metadata, author, body text, and reply status. Useful for sentiment analysis, support workflows, and review management.",
|
|
1651
|
+
getReviewsSchema,
|
|
1652
|
+
getReviews,
|
|
1653
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1079
1654
|
);
|
|
1080
|
-
|
|
1655
|
+
tool(
|
|
1656
|
+
"generate_review_reply",
|
|
1657
|
+
"Generate review reply (AI draft)",
|
|
1658
|
+
"Generate an AI draft reply for an app review. The AI uses the review context and any configured agent settings (tone, custom instructions) to craft a response. Returns the generated draft text. Does not publish \u2014 call send_review_reply or approve_action to publish the draft.",
|
|
1659
|
+
generateReviewReplySchema,
|
|
1660
|
+
generateReviewReply,
|
|
1661
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1662
|
+
);
|
|
1663
|
+
tool(
|
|
1664
|
+
"send_review_reply",
|
|
1665
|
+
"Send review reply",
|
|
1666
|
+
"Send a reply to an app review on the App Store or Google Play. The response text will be submitted as the developer response. This is a write operation that publishes the reply publicly \u2014 treat as destructive (cannot be silently undone).",
|
|
1667
|
+
sendReviewReplySchema,
|
|
1668
|
+
sendReviewReply,
|
|
1669
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
1670
|
+
);
|
|
1671
|
+
tool(
|
|
1672
|
+
"translate_review",
|
|
1673
|
+
"Translate review",
|
|
1674
|
+
"Translate a review to English. Useful for reviews written in other languages. Returns the translated text. Does not modify the review in Fload.",
|
|
1675
|
+
translateReviewSchema,
|
|
1676
|
+
translateReview,
|
|
1677
|
+
{ readOnlyHint: true, openWorldHint: true }
|
|
1678
|
+
);
|
|
1679
|
+
tool(
|
|
1081
1680
|
"discover_metrics",
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1681
|
+
"Discover available metrics",
|
|
1682
|
+
"Discover what metrics are available for an app. Returns all available metrics organized by category (revenue, downloads, subscriptions, engagement, ads). Always call this first before querying metrics to know what data exists.",
|
|
1683
|
+
discoverMetricsSchema,
|
|
1684
|
+
discoverMetrics,
|
|
1685
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1087
1686
|
);
|
|
1088
|
-
|
|
1687
|
+
tool(
|
|
1089
1688
|
"get_metrics",
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1689
|
+
"Get metrics",
|
|
1690
|
+
"Query metric timeseries data for an app. Supports 30+ metrics (proceeds, totalDownloads, activeSubs, sessions, crashes, adSpend, etc.). Can query multiple metrics at once. Supports dimensional breakdowns (by country, platform, campaign). Use discover_metrics first to see available metrics.",
|
|
1691
|
+
getMetricsSchema,
|
|
1692
|
+
getMetrics,
|
|
1693
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1095
1694
|
);
|
|
1096
|
-
|
|
1695
|
+
tool(
|
|
1097
1696
|
"discover_dimensions",
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1697
|
+
"Discover available dimensions",
|
|
1698
|
+
"Discover available dimensions for breaking down metrics (e.g., country, platform, app version, campaign). Optionally get the available values for a specific dimension.",
|
|
1699
|
+
discoverDimensionsSchema,
|
|
1700
|
+
discoverDimensions,
|
|
1701
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1103
1702
|
);
|
|
1104
|
-
|
|
1703
|
+
tool(
|
|
1105
1704
|
"list_agents",
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1705
|
+
"List agents",
|
|
1706
|
+
"List all available AI agents in the Fload platform with their current status. Returns agent types (review, monitoring, forecasting, growth, aso, ads, product, submission_review) and configuration status.",
|
|
1707
|
+
listAgentsSchema,
|
|
1708
|
+
listAgents,
|
|
1709
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1111
1710
|
);
|
|
1112
|
-
|
|
1711
|
+
tool(
|
|
1113
1712
|
"get_agent_details",
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1713
|
+
"Get agent details",
|
|
1714
|
+
"Get detailed configuration and status for a specific agent type. For the review agent, returns per-asset settings (mode, tone, custom instructions). For the product agent, returns latest run details.",
|
|
1715
|
+
getAgentDetailsSchema,
|
|
1716
|
+
getAgentDetails,
|
|
1717
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1119
1718
|
);
|
|
1120
|
-
|
|
1719
|
+
tool(
|
|
1121
1720
|
"get_agent_run_history",
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1721
|
+
"Get agent run history",
|
|
1722
|
+
"Get run history for a specific agent type. Currently available for the product agent (BrowserStack app installation runs). Returns run status, timing, and error details.",
|
|
1723
|
+
getAgentRunHistorySchema,
|
|
1724
|
+
getAgentRunHistory,
|
|
1725
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1726
|
+
);
|
|
1727
|
+
tool(
|
|
1728
|
+
"trigger_agent_run",
|
|
1729
|
+
"Trigger agent run",
|
|
1730
|
+
"Trigger a manual run for an agent. Optionally specify an asset (app) to run the agent against. Returns the triggered run details. Reversible in the sense that agent runs can be paused or dismissed.",
|
|
1731
|
+
triggerAgentRunSchema,
|
|
1732
|
+
triggerAgentRun,
|
|
1733
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1734
|
+
);
|
|
1735
|
+
tool(
|
|
1736
|
+
"pause_agent",
|
|
1737
|
+
"Pause agent",
|
|
1738
|
+
"Pause a running agent. The agent will stop processing until resumed. Reversible via resume_agent.",
|
|
1739
|
+
pauseAgentSchema,
|
|
1740
|
+
pauseAgent,
|
|
1741
|
+
{ readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1742
|
+
);
|
|
1743
|
+
tool(
|
|
1744
|
+
"resume_agent",
|
|
1745
|
+
"Resume agent",
|
|
1746
|
+
"Resume a paused agent. The agent will continue processing from where it left off. Reversible via pause_agent.",
|
|
1747
|
+
resumeAgentSchema,
|
|
1748
|
+
resumeAgent,
|
|
1749
|
+
{ readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1127
1750
|
);
|
|
1128
|
-
|
|
1751
|
+
tool(
|
|
1752
|
+
"get_agent_activity",
|
|
1753
|
+
"Get agent activity",
|
|
1754
|
+
"Get the recent activity log for an agent. Returns a chronological list of actions the agent has taken, including timestamps, event types, and details.",
|
|
1755
|
+
getAgentActivitySchema,
|
|
1756
|
+
getAgentActivity,
|
|
1757
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1758
|
+
);
|
|
1759
|
+
tool(
|
|
1129
1760
|
"get_anomalies",
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1761
|
+
"Get anomalies",
|
|
1762
|
+
"Get detected anomalies (unusual metric changes) for your apps. Filter by app, severity (low/medium/high/critical), type (surge/decline), status, metric name, and date range. Returns actual vs expected values, deviation percentage, confidence, and suggested actions.",
|
|
1763
|
+
getAnomaliesSchema,
|
|
1764
|
+
getAnomalies,
|
|
1765
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1766
|
+
);
|
|
1767
|
+
tool(
|
|
1768
|
+
"get_anomaly_detail",
|
|
1769
|
+
"Get anomaly detail",
|
|
1770
|
+
"Get full detail for a single anomaly including chart data. Returns the anomaly metadata, actual vs expected values, and historical metric data points for visualization.",
|
|
1771
|
+
getAnomalyDetailSchema,
|
|
1772
|
+
getAnomalyDetail,
|
|
1773
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1774
|
+
);
|
|
1775
|
+
tool(
|
|
1776
|
+
"acknowledge_anomaly",
|
|
1777
|
+
"Acknowledge anomaly",
|
|
1778
|
+
'Mark an anomaly as acknowledged. This updates the anomaly status from "new" to "acknowledged", indicating it has been reviewed but not dismissed. Reversible \u2014 anomaly can be reset or dismissed later.',
|
|
1779
|
+
acknowledgeAnomalySchema,
|
|
1780
|
+
acknowledgeAnomaly,
|
|
1781
|
+
{ readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1782
|
+
);
|
|
1783
|
+
tool(
|
|
1784
|
+
"dismiss_anomaly",
|
|
1785
|
+
"Dismiss anomaly",
|
|
1786
|
+
'Dismiss an anomaly. This updates the anomaly status to "dismissed", removing it from active alerts. Dismissed anomalies are excluded from queries by default. Soft state change \u2014 the anomaly row is preserved and can be un-dismissed later.',
|
|
1787
|
+
dismissAnomalySchema,
|
|
1788
|
+
dismissAnomaly,
|
|
1789
|
+
{ readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1135
1790
|
);
|
|
1136
|
-
|
|
1791
|
+
tool(
|
|
1137
1792
|
"get_ads_performance",
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1793
|
+
"Get ads performance",
|
|
1794
|
+
"Get ad campaign performance data across platforms (Apple Search Ads, Google Ads, Meta Ads, TikTok Ads). Returns campaign metadata, status, and for Apple Search Ads includes daily performance snapshots (spend, impressions, taps, installs, CPI, TTR, conversion rate).",
|
|
1795
|
+
getAdsPerformanceSchema,
|
|
1796
|
+
getAdsPerformance,
|
|
1797
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1143
1798
|
);
|
|
1144
|
-
|
|
1799
|
+
tool(
|
|
1145
1800
|
"get_growth_audit",
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1801
|
+
"Get growth audit",
|
|
1802
|
+
"Get a comprehensive growth audit for an app. Synthesizes data from review sentiment analysis, recent anomalies, valuation trends, and connector health into an actionable growth assessment.",
|
|
1803
|
+
getGrowthAuditSchema,
|
|
1804
|
+
getGrowthAudit,
|
|
1805
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1151
1806
|
);
|
|
1152
|
-
|
|
1807
|
+
tool(
|
|
1153
1808
|
"get_growth_score",
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1809
|
+
"Get growth score",
|
|
1810
|
+
"Get a calculated growth score (0-100) and grade (A-F) for an app. The score is based on app store rating, valuation trend, recent anomalies, review sentiment, and data connector health. Includes a breakdown of scoring factors.",
|
|
1811
|
+
getGrowthScoreSchema,
|
|
1812
|
+
getGrowthScore,
|
|
1813
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1159
1814
|
);
|
|
1160
|
-
|
|
1815
|
+
tool(
|
|
1161
1816
|
"get_forecasts",
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1817
|
+
"Get forecasts",
|
|
1818
|
+
"Get valuation-based forecasts and trend analysis for an app. Returns historical valuation data points, trend statistics (direction, volatility), and simple linear projections. For detailed metric forecasting with statistical models, use the platform dashboard.",
|
|
1819
|
+
getForecastsSchema,
|
|
1820
|
+
getForecasts,
|
|
1821
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1167
1822
|
);
|
|
1168
|
-
|
|
1823
|
+
tool(
|
|
1169
1824
|
"get_dashboard_overview",
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1825
|
+
"Get dashboard overview",
|
|
1826
|
+
"Get an aggregated dashboard overview for the organization. Returns portfolio summary (apps, valuations, ratings), data connector health status, and alerts (recent anomalies, pending review drafts).",
|
|
1827
|
+
getDashboardOverviewSchema,
|
|
1828
|
+
getDashboardOverview,
|
|
1829
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1175
1830
|
);
|
|
1176
|
-
|
|
1831
|
+
tool(
|
|
1177
1832
|
"list_pending_actions",
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1833
|
+
"List pending actions",
|
|
1834
|
+
"List pending actions awaiting approval. Currently shows AI-generated review draft replies that have not been sent yet. Includes the original review context and the drafted reply. Filter by app.",
|
|
1835
|
+
listPendingActionsSchema,
|
|
1836
|
+
listPendingActions,
|
|
1837
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1183
1838
|
);
|
|
1184
|
-
|
|
1839
|
+
tool(
|
|
1185
1840
|
"approve_action",
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1841
|
+
"Approve pending action",
|
|
1842
|
+
"Approve a pending action (e.g., a review draft reply). Approving a review reply publishes it to the store \u2014 treat as destructive (publicly visible, cannot be silently undone). Optionally edit the reply text before approving.",
|
|
1843
|
+
approveActionSchema,
|
|
1844
|
+
approveAction,
|
|
1845
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
1191
1846
|
);
|
|
1192
|
-
|
|
1847
|
+
tool(
|
|
1193
1848
|
"reject_action",
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1849
|
+
"Reject pending action",
|
|
1850
|
+
"Reject a pending action (e.g., delete a review draft reply). The draft will be permanently removed. Destructive \u2014 not recoverable without regenerating the draft.",
|
|
1851
|
+
rejectActionSchema,
|
|
1852
|
+
rejectAction,
|
|
1853
|
+
{ readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
1854
|
+
);
|
|
1855
|
+
tool(
|
|
1856
|
+
"get_aso_summary",
|
|
1857
|
+
"Get ASO summary",
|
|
1858
|
+
"Get the ASO (App Store Optimization) score, health status, and overview for an app. Returns an overall optimization score and key ASO health indicators. Use this for a quick snapshot of how well an app is optimized for store search and discovery.",
|
|
1859
|
+
getAsoSummarySchema,
|
|
1860
|
+
getAsoSummary,
|
|
1861
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1862
|
+
);
|
|
1863
|
+
tool(
|
|
1864
|
+
"get_aso_recommendations",
|
|
1865
|
+
"Get ASO recommendations",
|
|
1866
|
+
"Get actionable ASO recommendations for an app, including suggested improvements to the title, subtitle, keywords, and description. Each recommendation explains the rationale and expected impact on search visibility.",
|
|
1867
|
+
getAsoRecommendationsSchema,
|
|
1868
|
+
getAsoRecommendations,
|
|
1869
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1870
|
+
);
|
|
1871
|
+
tool(
|
|
1872
|
+
"get_aso_keywords",
|
|
1873
|
+
"Get ASO keywords",
|
|
1874
|
+
'Get keyword intelligence for an app \u2014 current keyword rankings, search volume estimates, difficulty scores, and competitor keyword data. Optionally filter by locale (e.g., "en-US"). Useful for identifying keyword opportunities and tracking ranking changes.',
|
|
1875
|
+
getAsoKeywordsSchema,
|
|
1876
|
+
getAsoKeywords,
|
|
1877
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1878
|
+
);
|
|
1879
|
+
tool(
|
|
1880
|
+
"get_aso_experiments",
|
|
1881
|
+
"Get ASO experiments",
|
|
1882
|
+
"List ASO experiments (A/B tests and metadata changes) for an app. Filter by status: proposed, approved, applied, measuring, completed, or reverted. Returns experiment details, variants, and results when available. Supports pagination.",
|
|
1883
|
+
getAsoExperimentsSchema,
|
|
1884
|
+
getAsoExperiments,
|
|
1885
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1886
|
+
);
|
|
1887
|
+
tool(
|
|
1888
|
+
"get_aso_locale_snapshots",
|
|
1889
|
+
"Get ASO locale snapshots",
|
|
1890
|
+
"Get current App Store and Google Play listing snapshots across all locales for an app. Returns the live title, subtitle, keywords, description, and promotional text for each locale. Useful for auditing localized metadata consistency.",
|
|
1891
|
+
getAsoLocaleSnapshotsSchema,
|
|
1892
|
+
getAsoLocaleSnapshots,
|
|
1893
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1894
|
+
);
|
|
1895
|
+
tool(
|
|
1896
|
+
"trigger_aso_analysis",
|
|
1897
|
+
"Trigger ASO analysis",
|
|
1898
|
+
"Trigger a new ASO analysis run for an app. This kicks off a fresh evaluation of the app's store listing metadata, keyword rankings, and competitive positioning. Results will be reflected in subsequent calls to get_aso_summary and get_aso_recommendations.",
|
|
1899
|
+
triggerAsoAnalysisSchema,
|
|
1900
|
+
triggerAsoAnalysis,
|
|
1901
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1902
|
+
);
|
|
1903
|
+
tool(
|
|
1904
|
+
"list_conversations",
|
|
1905
|
+
"List conversations",
|
|
1906
|
+
"List chat conversations in the Fload AI chat. Returns conversation metadata including title, creation date, and last message preview. Use limit to control how many are returned.",
|
|
1907
|
+
listConversationsSchema,
|
|
1908
|
+
listConversations,
|
|
1909
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1910
|
+
);
|
|
1911
|
+
tool(
|
|
1912
|
+
"get_conversation_messages",
|
|
1913
|
+
"Get conversation messages",
|
|
1914
|
+
"Get all messages in a specific chat conversation. Returns the full message history including user messages and AI responses with timestamps and roles.",
|
|
1915
|
+
getConversationMessagesSchema,
|
|
1916
|
+
getConversationMessages,
|
|
1917
|
+
{ readOnlyHint: true, openWorldHint: false }
|
|
1918
|
+
);
|
|
1919
|
+
tool(
|
|
1920
|
+
"send_chat_message",
|
|
1921
|
+
"Send chat message",
|
|
1922
|
+
"Send a message to the Fload AI chat assistant. Starts a new conversation if no conversationId is provided, or continues an existing one. Optionally specify an agentType to route to a specialized agent (review, monitoring, forecasting, etc.). Adds a message to your chat history \u2014 not publicly visible.",
|
|
1923
|
+
sendChatMessageSchema,
|
|
1924
|
+
sendChatMessage,
|
|
1925
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1199
1926
|
);
|
|
1200
|
-
|
|
1201
|
-
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// src/server.ts
|
|
1930
|
+
async function createMcpServer(_config, client) {
|
|
1931
|
+
console.error("[Fload MCP] Server initializing...");
|
|
1932
|
+
const server = new McpServer({
|
|
1933
|
+
name: "fload",
|
|
1934
|
+
version: "0.1.0"
|
|
1935
|
+
});
|
|
1936
|
+
registerTools(server, client);
|
|
1937
|
+
console.error("[Fload MCP] Registered tools");
|
|
1202
1938
|
return server;
|
|
1203
1939
|
}
|
|
1204
1940
|
|
|
1205
|
-
// src/
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
headers: {
|
|
1216
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
1217
|
-
"Content-Type": "application/json",
|
|
1218
|
-
...options.headers
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
if (!response.ok) {
|
|
1222
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1223
|
-
throw new Error(error.message || `API error: ${response.status}`);
|
|
1224
|
-
}
|
|
1225
|
-
return response.json();
|
|
1226
|
-
}
|
|
1227
|
-
async get(path, params) {
|
|
1228
|
-
const searchParams = new URLSearchParams();
|
|
1229
|
-
if (params) {
|
|
1230
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1231
|
-
if (value !== void 0)
|
|
1232
|
-
searchParams.set(key, String(value));
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
const query = searchParams.toString();
|
|
1236
|
-
return this.request(`${path}${query ? `?${query}` : ""}`);
|
|
1237
|
-
}
|
|
1238
|
-
async post(path, body) {
|
|
1239
|
-
return this.request(path, {
|
|
1240
|
-
method: "POST",
|
|
1241
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
async patch(path, body) {
|
|
1245
|
-
return this.request(path, {
|
|
1246
|
-
method: "PATCH",
|
|
1247
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
async delete(path) {
|
|
1251
|
-
return this.request(path, { method: "DELETE" });
|
|
1941
|
+
// src/config.ts
|
|
1942
|
+
import "dotenv/config";
|
|
1943
|
+
import { readFileSync, existsSync } from "fs";
|
|
1944
|
+
import { homedir } from "os";
|
|
1945
|
+
import { join } from "path";
|
|
1946
|
+
function loadConfig() {
|
|
1947
|
+
const apiKey = process.env.FLOAD_API_KEY;
|
|
1948
|
+
const apiUrl = process.env.FLOAD_API_URL || "https://api.fload.com";
|
|
1949
|
+
if (apiKey) {
|
|
1950
|
+
return { apiKey, apiUrl };
|
|
1252
1951
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
// src/index.ts
|
|
1256
|
-
async function main() {
|
|
1257
|
-
try {
|
|
1258
|
-
console.error("[Fload MCP] Starting server...");
|
|
1259
|
-
const config = loadConfig();
|
|
1260
|
-
console.error("[Fload MCP] Configuration loaded");
|
|
1261
|
-
console.error(`[Fload MCP] API URL: ${config.apiUrl}`);
|
|
1262
|
-
const client = new FloadApiClient(config.apiUrl, config.apiKey);
|
|
1952
|
+
const configPath = join(homedir(), ".fload", "config.json");
|
|
1953
|
+
if (existsSync(configPath)) {
|
|
1263
1954
|
try {
|
|
1264
|
-
|
|
1265
|
-
|
|
1955
|
+
const configData = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1956
|
+
if (configData.apiKey) {
|
|
1957
|
+
return {
|
|
1958
|
+
apiKey: configData.apiKey,
|
|
1959
|
+
apiUrl: configData.apiUrl || apiUrl
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1266
1962
|
} catch (error) {
|
|
1267
|
-
console.error("
|
|
1268
|
-
console.error("[Fload MCP] Continuing anyway \u2014 API may become available");
|
|
1963
|
+
console.error("Failed to parse config file:", error);
|
|
1269
1964
|
}
|
|
1270
|
-
const server = await createMcpServer(config, client);
|
|
1271
|
-
const transport = new StdioServerTransport();
|
|
1272
|
-
await server.connect(transport);
|
|
1273
|
-
console.error("[Fload MCP] Server running on stdio");
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
console.error("[Fload MCP] Fatal error:", error);
|
|
1276
|
-
process.exit(1);
|
|
1277
1965
|
}
|
|
1966
|
+
throw new Error(
|
|
1967
|
+
'Fload MCP requires an API key. Set FLOAD_API_KEY environment variable or create ~/.fload/config.json with {"apiKey": "fload_sk_..."}'
|
|
1968
|
+
);
|
|
1278
1969
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
});
|
|
1287
|
-
main().catch((error) => {
|
|
1288
|
-
console.error("[Fload MCP] Unhandled error:", error);
|
|
1289
|
-
process.exit(1);
|
|
1290
|
-
});
|
|
1970
|
+
export {
|
|
1971
|
+
FloadApiClient,
|
|
1972
|
+
RateLimiter,
|
|
1973
|
+
createMcpServer,
|
|
1974
|
+
loadConfig,
|
|
1975
|
+
registerTools
|
|
1976
|
+
};
|
|
1291
1977
|
//# sourceMappingURL=index.js.map
|