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.
@@ -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] || "node";
2537
- const args = serverCommand?.length ? serverCommand.slice(1) : ["server.js"];
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
- if (competitors.length > 0) {
2583
- const first = competitors[0];
2602
+ const firstCompId = competitors[0]?.appId || raw.searchAppFallbackApps[0];
2603
+ if (firstCompId) {
2584
2604
  const res = await callToolJSON(client, "get_similar_apps", {
2585
- appId: first.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 = competitors[0]?.appId;
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: recommended.map((r) => ({
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: "Sorted by traffic score (desc). Blend of core/high-traffic and mid/longtail technical terms.",
2720
- nextActions: "Feed top 10\u201315 into improve-public Stage 1. If any sections are empty (suggestions/reviews), add more competitors/seeds and rerun."
2774
+ rationale: rationaleText,
2775
+ nextActions: nextActionsText
2721
2776
  };
2722
2777
  await client.close();
2723
2778
  const researchDir = path11.join(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-web-mcp",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",