pabal-web-mcp 1.3.1 → 1.3.3

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.
@@ -2448,6 +2448,9 @@ var keywordResearchInputSchema = z6.object({
2448
2448
  writeTemplate: z6.boolean().default(false).describe("If true, write a JSON template at the output path."),
2449
2449
  researchData: z6.string().trim().optional().describe(
2450
2450
  "Optional JSON string with research results (e.g., from mcp-appstore tools). If provided, saves it to the output path."
2451
+ ),
2452
+ researchDataPath: z6.string().trim().optional().describe(
2453
+ "Optional path to a JSON file containing research results. If set, file content is saved to the output path (preferred to avoid escape errors)."
2451
2454
  )
2452
2455
  });
2453
2456
  var jsonSchema6 = zodToJsonSchema6(keywordResearchInputSchema, {
@@ -2489,9 +2492,10 @@ function buildTemplate({
2489
2492
  "Discover competitors: search_app(term=seed keyword), get_similar_apps(appId=known competitor).",
2490
2493
  "Collect candidates: suggest_keywords_by_seeds/by_category/by_similarity/by_competition/by_search + suggest_keywords_by_apps(apps=[top competitors]).",
2491
2494
  "Score shortlist: get_keyword_scores for 15\u201330 candidates per platform/country.",
2492
- "Context check: analyze_reviews and fetch_reviews on top apps for language/tone cues."
2495
+ "Context check: analyze_reviews and fetch_reviews on top apps for language/tone cues.",
2496
+ "If keywordSuggestions/similar/reviews are sparse, rerun calls (add more competitors/seeds) until you have 10\u201315 strong keywords."
2493
2497
  ],
2494
- note: "Run per platform/country. Save raw tool outputs plus curated top keywords."
2498
+ note: "Run per platform/country. Save raw tool outputs plus curated top keywords (target 10\u201315 per locale: 2\u20133 high-traffic core, 4\u20136 mid-competition, 4\u20136 longtail)."
2495
2499
  },
2496
2500
  data: {
2497
2501
  raw: {
@@ -2513,7 +2517,7 @@ function buildTemplate({
2513
2517
  summary: {
2514
2518
  recommendedKeywords: [],
2515
2519
  rationale: "",
2516
- nextActions: "Feed top 10\u201315 into improve-public Stage 1."
2520
+ nextActions: "Feed 10\u201315 mixed keywords (core/mid/longtail) into improve-public Stage 1."
2517
2521
  }
2518
2522
  }
2519
2523
  };
@@ -2545,7 +2549,8 @@ async function handleKeywordResearch(input) {
2545
2549
  competitorApps = [],
2546
2550
  filename,
2547
2551
  writeTemplate = false,
2548
- researchData
2552
+ researchData,
2553
+ researchDataPath
2549
2554
  } = input;
2550
2555
  const { config, locales } = loadProductLocales(slug);
2551
2556
  const primaryLocale = resolvePrimaryLocale(config, locales);
@@ -2608,16 +2613,34 @@ async function handleKeywordResearch(input) {
2608
2613
  const fileName = filename || defaultFileName;
2609
2614
  let outputPath = path11.join(researchDir, fileName);
2610
2615
  let fileAction;
2611
- if (writeTemplate || researchData) {
2612
- const payload = researchData ? (() => {
2613
- try {
2614
- return JSON.parse(researchData);
2615
- } catch (err) {
2616
+ const parseJsonWithContext = (text) => {
2617
+ try {
2618
+ return JSON.parse(text);
2619
+ } catch (err) {
2620
+ const message = err instanceof Error ? err.message : String(err);
2621
+ const match = /position (\d+)/i.exec(message) || /column (\d+)/i.exec(message) || /char (\d+)/i.exec(message);
2622
+ if (match) {
2623
+ const pos = Number(match[1]);
2624
+ const start = Math.max(0, pos - 40);
2625
+ const end = Math.min(text.length, pos + 40);
2626
+ const context = text.slice(start, end);
2616
2627
  throw new Error(
2617
- `Failed to parse researchData JSON: ${err instanceof Error ? err.message : String(err)}`
2628
+ `Failed to parse researchData JSON: ${message}
2629
+ Context around ${pos}: ${context}`
2618
2630
  );
2619
2631
  }
2620
- })() : buildTemplate({
2632
+ throw new Error(`Failed to parse researchData JSON: ${message}`);
2633
+ }
2634
+ };
2635
+ const loadResearchDataFromPath = (p) => {
2636
+ if (!fs11.existsSync(p)) {
2637
+ throw new Error(`researchDataPath not found: ${p}`);
2638
+ }
2639
+ const raw = fs11.readFileSync(p, "utf-8");
2640
+ return parseJsonWithContext(raw);
2641
+ };
2642
+ if (writeTemplate || researchData) {
2643
+ const payload = researchData ? parseJsonWithContext(researchData) : researchDataPath ? loadResearchDataFromPath(researchDataPath) : buildTemplate({
2621
2644
  slug,
2622
2645
  locale,
2623
2646
  platform,
@@ -2687,7 +2710,10 @@ async function handleKeywordResearch(input) {
2687
2710
  `6) Context check: analyze_reviews and fetch_reviews on top apps to harvest native phrasing; keep snippets for improve-public.`
2688
2711
  );
2689
2712
  lines.push(
2690
- `7) Save all raw responses + your final top 10\u201315 keywords to: ${outputPath} (structure mirrors .aso/pullData/.aso/pushData under products/<slug>/locales/<locale>)`
2713
+ `7) Save all raw responses + your final 10\u201315 keywords (mix of core/high-traffic, mid, longtail) to: ${outputPath} (structure mirrors .aso/pullData/.aso/pushData under products/<slug>/locales/<locale>)`
2714
+ );
2715
+ lines.push(
2716
+ `8) If keywordSuggestions/similarApps/reviews are still empty or <10 solid candidates, add more competitors/seeds and rerun the calls above until you reach 10\u201315 strong keywords.`
2691
2717
  );
2692
2718
  if (fileAction) {
2693
2719
  lines.push(`File: ${fileAction} at ${outputPath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-web-mcp",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",