pabal-web-mcp 1.3.6 → 1.3.7
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/dist/bin/mcp-server.js +65 -10
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -2533,8 +2533,20 @@ async function handleKeywordResearch(input) {
|
|
|
2533
2533
|
).values()
|
|
2534
2534
|
).slice(0, 5);
|
|
2535
2535
|
const cwd = serverCwd || path11.resolve(process.cwd(), "external-tools", "mcp-appstore");
|
|
2536
|
-
const cmd = serverCommand?.[0] || "
|
|
2537
|
-
const args = serverCommand?.length ? serverCommand.slice(1) : ["
|
|
2536
|
+
const cmd = serverCommand?.[0] || "npm";
|
|
2537
|
+
const args = serverCommand?.length ? serverCommand.slice(1) : ["start"];
|
|
2538
|
+
if (!fs11.existsSync(cwd)) {
|
|
2539
|
+
throw new Error(
|
|
2540
|
+
`mcp-appstore cwd not found: ${cwd}. Set 'serverCwd' to the absolute path (e.g., /Users/you/pabal-web-mcp/external-tools/mcp-appstore).`
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
const serverJsPath = path11.join(cwd, "server.js");
|
|
2544
|
+
const usingNodeServer = cmd === "node" || args.includes("server.js");
|
|
2545
|
+
if (usingNodeServer && !fs11.existsSync(serverJsPath)) {
|
|
2546
|
+
throw new Error(
|
|
2547
|
+
`mcp-appstore server.js not found under ${cwd}. Set 'serverCommand' (e.g., ["npm","start"] or ["node","/abs/path/server.js"]).`
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2538
2550
|
const transport = new StdioClientTransport({ command: cmd, args, cwd });
|
|
2539
2551
|
const client = new Client(
|
|
2540
2552
|
{ name: "keyword-research", version: "1.0.0" },
|
|
@@ -2545,6 +2557,7 @@ async function handleKeywordResearch(input) {
|
|
|
2545
2557
|
searchApp: [],
|
|
2546
2558
|
getAppDetails: [],
|
|
2547
2559
|
similarApps: [],
|
|
2560
|
+
searchAppFallbackApps: [],
|
|
2548
2561
|
keywordSuggestions: {
|
|
2549
2562
|
bySeeds: [],
|
|
2550
2563
|
byCategory: [],
|
|
@@ -2557,6 +2570,7 @@ async function handleKeywordResearch(input) {
|
|
|
2557
2570
|
reviewsAnalysis: [],
|
|
2558
2571
|
reviewsRaw: []
|
|
2559
2572
|
};
|
|
2573
|
+
const fallbackAppIds = [];
|
|
2560
2574
|
for (const term of seeds.slice(0, 3)) {
|
|
2561
2575
|
const res = await callToolJSON(client, "search_app", {
|
|
2562
2576
|
term,
|
|
@@ -2570,7 +2584,13 @@ async function handleKeywordResearch(input) {
|
|
|
2570
2584
|
country: resolvedCountry,
|
|
2571
2585
|
results: res?.results ?? res
|
|
2572
2586
|
});
|
|
2587
|
+
const apps = (res?.results ?? res) || [];
|
|
2588
|
+
apps.slice(0, 5).forEach((app) => {
|
|
2589
|
+
const appId = app?.appId || app?.id;
|
|
2590
|
+
if (appId) fallbackAppIds.push(String(appId));
|
|
2591
|
+
});
|
|
2573
2592
|
}
|
|
2593
|
+
raw.searchAppFallbackApps = Array.from(new Set(fallbackAppIds));
|
|
2574
2594
|
for (const comp of competitors.slice(0, 5)) {
|
|
2575
2595
|
const res = await callToolJSON(client, "get_app_details", {
|
|
2576
2596
|
appId: comp.appId,
|
|
@@ -2579,18 +2599,18 @@ async function handleKeywordResearch(input) {
|
|
|
2579
2599
|
});
|
|
2580
2600
|
raw.getAppDetails.push(res);
|
|
2581
2601
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2602
|
+
const firstCompId = competitors[0]?.appId || raw.searchAppFallbackApps[0];
|
|
2603
|
+
if (firstCompId) {
|
|
2584
2604
|
const res = await callToolJSON(client, "get_similar_apps", {
|
|
2585
|
-
appId:
|
|
2605
|
+
appId: firstCompId,
|
|
2586
2606
|
platform,
|
|
2587
2607
|
num: 10,
|
|
2588
2608
|
country: resolvedCountry
|
|
2589
2609
|
});
|
|
2590
2610
|
raw.similarApps = res?.similarApps || res || [];
|
|
2591
2611
|
}
|
|
2592
|
-
const firstComp =
|
|
2593
|
-
const appsList = competitors.map((c) => c.appId);
|
|
2612
|
+
const firstComp = firstCompId;
|
|
2613
|
+
const appsList = competitors.length ? competitors.map((c) => c.appId) : raw.searchAppFallbackApps.slice(0, 5);
|
|
2594
2614
|
const suggestCalls = [
|
|
2595
2615
|
[
|
|
2596
2616
|
"suggest_keywords_by_seeds",
|
|
@@ -2675,6 +2695,23 @@ async function handleKeywordResearch(input) {
|
|
|
2675
2695
|
});
|
|
2676
2696
|
raw.reviewsRaw.push(rawReviews);
|
|
2677
2697
|
}
|
|
2698
|
+
if (competitors.length === 0 && raw.searchAppFallbackApps.length > 0) {
|
|
2699
|
+
const fb = raw.searchAppFallbackApps[0];
|
|
2700
|
+
const analysis = await callToolJSON(client, "analyze_reviews", {
|
|
2701
|
+
appId: fb,
|
|
2702
|
+
platform,
|
|
2703
|
+
country: resolvedCountry,
|
|
2704
|
+
num: 150
|
|
2705
|
+
});
|
|
2706
|
+
raw.reviewsAnalysis.push(analysis);
|
|
2707
|
+
const rawReviews = await callToolJSON(client, "fetch_reviews", {
|
|
2708
|
+
appId: fb,
|
|
2709
|
+
platform,
|
|
2710
|
+
country: resolvedCountry,
|
|
2711
|
+
num: 80
|
|
2712
|
+
});
|
|
2713
|
+
raw.reviewsRaw.push(rawReviews);
|
|
2714
|
+
}
|
|
2678
2715
|
const candidateSet = /* @__PURE__ */ new Set();
|
|
2679
2716
|
const addCandidates = (arr) => {
|
|
2680
2717
|
if (!arr) return;
|
|
@@ -2688,6 +2725,12 @@ async function handleKeywordResearch(input) {
|
|
|
2688
2725
|
};
|
|
2689
2726
|
seeds.forEach((s) => candidateSet.add(s));
|
|
2690
2727
|
Object.values(raw.keywordSuggestions).forEach((arr) => addCandidates(arr));
|
|
2728
|
+
raw.reviewsAnalysis.forEach((ra) => {
|
|
2729
|
+
const freq = ra?.analysis?.keywordFrequency || ra?.keywordFrequency;
|
|
2730
|
+
if (freq && typeof freq === "object") {
|
|
2731
|
+
addCandidates(Object.keys(freq));
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2691
2734
|
const candidates = Array.from(candidateSet).slice(0, 30);
|
|
2692
2735
|
for (const kw of candidates) {
|
|
2693
2736
|
const res = await callToolJSON(client, "get_keyword_scores", {
|
|
@@ -2710,14 +2753,26 @@ async function handleKeywordResearch(input) {
|
|
|
2710
2753
|
};
|
|
2711
2754
|
}).filter((s) => s.keyword);
|
|
2712
2755
|
const recommended = scored.sort((a, b) => b.trafficScore - a.trafficScore).slice(0, 15);
|
|
2756
|
+
const recommendedKeywords = recommended.map((r) => r.keyword).filter(Boolean);
|
|
2757
|
+
const top1 = recommended[0];
|
|
2758
|
+
const top3 = recommendedKeywords.slice(0, 3);
|
|
2759
|
+
const rationaleText = top1 && top1.keyword ? `Highest traffic: ${top1.keyword} (traffic ${top1.trafficScore.toFixed(
|
|
2760
|
+
2
|
|
2761
|
+
)}, difficulty ${top1.difficultyScore.toFixed(
|
|
2762
|
+
2
|
|
2763
|
+
)}). Blend core/high-traffic and mid/longtail terms for coverage.` : "Sorted by traffic score (desc). Blend of core/high-traffic and mid/longtail technical terms.";
|
|
2764
|
+
const nextActionsText = recommendedKeywords.length ? `Use top keywords in improve-public Stage 1. Emphasize: ${top3.join(
|
|
2765
|
+
", "
|
|
2766
|
+
)}. Keep app name in English; target 10\u201315 keywords in keyword fields.` : "Run again to produce at least 10\u201315 keywords, then feed into improve-public Stage 1.";
|
|
2713
2767
|
const summary = {
|
|
2714
|
-
recommendedKeywords
|
|
2768
|
+
recommendedKeywords,
|
|
2769
|
+
recommendedDetails: recommended.map((r) => ({
|
|
2715
2770
|
keyword: r.keyword,
|
|
2716
2771
|
trafficScore: r.trafficScore,
|
|
2717
2772
|
difficultyScore: r.difficultyScore
|
|
2718
2773
|
})),
|
|
2719
|
-
rationale:
|
|
2720
|
-
nextActions:
|
|
2774
|
+
rationale: rationaleText,
|
|
2775
|
+
nextActions: nextActionsText
|
|
2721
2776
|
};
|
|
2722
2777
|
await client.close();
|
|
2723
2778
|
const researchDir = path11.join(
|